Skip to content

Commit 9c0f0fa

Browse files
committed
Consolidate docs
1 parent 28e1ebb commit 9c0f0fa

File tree

15 files changed

+862
-257
lines changed

15 files changed

+862
-257
lines changed

book/book.toml

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,5 @@
11
[book]
2-
title = "Ruby on Rust"
3-
description = "The definitive guide for using Rust in Ruby"
42
authors = ["Ian Ker-Seymer"]
53
language = "en"
6-
multilingual = false
74
src = "src"
8-
9-
[output.html]
10-
git-repository-url = "https://github.com/oxidize-rb/rb-sys"
11-
edit-url-template = "https://github.com/oxidize-rb/rb-sys/edit/main/book/{path}"
12-
site-url = "/rb-sys/"
13-
14-
[output.html.playground]
15-
editable = true
16-
line-numbers = true
17-
18-
[output.html.code]
19-
20-
[output.html.code.hidelines]
21-
ruby = "#"
22-
bash = "#"
23-
24-
[rust]
25-
edition = "2021"
5+
title = "The Ruby on Rust Book"

book/src/SUMMARY.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@
2828
- [Debugging & Troubleshooting](./debugging.md)
2929
- [Troubleshooting Guide](./troubleshooting.md)
3030

31+
## API Reference
32+
33+
- [rb-sys Crate Features](./api-reference/rb-sys-features.md)
34+
- [rb_sys Gem Configuration](./api-reference/rb-sys-gem-config.md)
35+
- [Test Helpers](./api-reference/test-helpers.md)
36+
37+
## Community and Support
38+
39+
- [Getting Help](./community-support.md)
40+
3141
---
3242

3343
[Contributing to rb-sys](https://github.com/oxidize-rb/rb-sys/blob/main/CONTRIBUTING.md)
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# rb-sys Crate Features
2+
3+
The `rb-sys` crate provides battle-tested Rust bindings for the Ruby C API. It uses the [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen) crate to generate bindings from the `ruby.h` header.
4+
5+
## Usage Notice
6+
7+
This is a very low-level library. If you are looking to write a gem in Rust, you should probably use the [Magnus](https://github.com/matsadler/magnus) crate with the `rb-sys-interop` feature, which provides a higher-level, more ergonomic API.
8+
9+
If you need raw/unsafe bindings to libruby, then this crate is for you!
10+
11+
## Writing a Ruby Gem
12+
13+
Ruby gems require boilerplate to be defined to be usable from Ruby. `rb-sys` makes this process painless by doing the work for you. Simply enable the `gem` feature:
14+
15+
```toml
16+
[dependencies]
17+
rb-sys = "0.9"
18+
```
19+
20+
Under the hood, this ensures the crate does not link libruby (unless on Windows) and defines a `ruby_abi_version` function for Ruby 3.2+.
21+
22+
## Embedding libruby in Your Rust App
23+
24+
**IMPORTANT**: If you are authoring a Ruby gem, you do not need to enable this feature.
25+
26+
If you need to link libruby (i.e., you are initializing a Ruby VM in your Rust code), you can enable the `link-ruby` feature:
27+
28+
```toml
29+
[dependencies]
30+
rb-sys = { version = "0.9", features = ["link-ruby"] }
31+
```
32+
33+
## Static libruby
34+
35+
You can also force static linking of libruby:
36+
37+
```toml
38+
[dependencies]
39+
rb-sys = { version = "0.9", features = ["ruby-static"] }
40+
```
41+
42+
Alternatively, you can set the `RUBY_STATIC=true` environment variable.
43+
44+
## Available Features
45+
46+
The `rb-sys` crate provides several features that can be enabled in your `Cargo.toml`:
47+
48+
| Feature | Description |
49+
|---------|-------------|
50+
| `global-allocator` | Report Rust memory allocations to the Ruby GC (_recommended_) |
51+
| `ruby-static` | Link the static version of libruby |
52+
| `link-ruby` | Link libruby (typically used for embedding, not for extensions) |
53+
| `bindgen-rbimpls` | Include the Ruby impl types in bindings |
54+
| `bindgen-deprecated-types` | Include deprecated Ruby methods in bindings |
55+
| `gem` | Set up the crate for use in a Ruby gem (default feature) |
56+
| `stable-api` | Use the stable API (C level) if available for your Ruby version |
57+
58+
## Example Cargo.toml
59+
60+
```toml
61+
[dependencies]
62+
rb-sys = { version = "0.9", features = ["global-allocator", "stable-api"] }
63+
```
64+
65+
## Ruby Version Compatibility
66+
67+
`rb-sys` is compatible with Ruby 2.6 and later. The crate detects the Ruby version at compile time and adapts the bindings accordingly.
68+
69+
For Ruby 3.2 and later, `rb-sys` provides a `ruby_abi_version` function that is required for native extensions.
70+
71+
## Integration with Magnus
72+
73+
If you're building a Ruby extension, it's recommended to use the [Magnus](https://github.com/matsadler/magnus) crate on top of `rb-sys`. Magnus provides a high-level, safe API for interacting with Ruby:
74+
75+
```toml
76+
[dependencies]
77+
magnus = { version = "0.7", features = ["rb-sys"] }
78+
rb-sys = "0.9"
79+
```
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# rb_sys Gem Configuration
2+
3+
The `rb_sys` gem makes it easy to build native Ruby extensions in Rust. It interoperates with existing Ruby native extension toolchains (i.e., `rake-compiler`) to make testing, building, and cross-compilation of gems easy.
4+
5+
## RbSys::ExtensionTask
6+
7+
This gem provides a `RbSys::ExtensionTask` class that can be used to build a Ruby extension in Rust. It's a thin wrapper around `Rake::ExtensionTask` that provides sane defaults for building Rust extensions.
8+
9+
```ruby
10+
# Rakefile
11+
12+
require "rb_sys/extensiontask"
13+
14+
GEMSPEC = Gem::Specification.load("my_gem.gemspec")
15+
16+
RbSys::ExtensionTask.new("my-crate-name", GEMSPEC) do |ext|
17+
ext.lib_dir = "lib/my_gem"
18+
19+
# If you want to use `rb-sys-dock` for cross-compilation:
20+
ext.cross_compile = true
21+
end
22+
```
23+
24+
## create_rust_makefile
25+
26+
The gem provides a simple helper to build a Ruby-compatible Makefile for your Rust extension:
27+
28+
```ruby
29+
# ext/rust_reverse/extconf.rb
30+
31+
# We need to require mkmf *first* since `rake-compiler` injects code here for cross compilation
32+
require "mkmf"
33+
require "rb_sys/mkmf"
34+
35+
create_rust_makefile("rust_reverse") do |r|
36+
# Create debug builds in dev. Make sure that release gems are compiled with
37+
# `RB_SYS_CARGO_PROFILE=release` (optional)
38+
r.profile = ENV.fetch("RB_SYS_CARGO_PROFILE", :dev).to_sym
39+
40+
# Can be overridden with `RB_SYS_CARGO_FEATURES` env var (optional)
41+
r.features = ["test-feature"]
42+
43+
# You can add whatever env vars you want to the env hash (optional)
44+
r.env = {"FOO" => "BAR"}
45+
46+
# If your Cargo.toml is in a different directory, you can specify it here (optional)
47+
r.ext_dir = "."
48+
49+
# Extra flags to pass to the $RUSTFLAGS environment variable (optional)
50+
r.extra_rustflags = ["--cfg=some_nested_config_var_for_crate"]
51+
52+
# Force a rust toolchain to be installed via rustup (optional)
53+
# You can also set the env var `RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN=true`
54+
r.force_install_rust_toolchain = "stable"
55+
56+
# Clean up the target/ dir after `gem install` to reduce bloat (optional)
57+
r.clean_after_install = false # default: true if invoked by rubygems
58+
59+
# Auto-install Rust toolchain if not present on "gem install" (optional)
60+
r.auto_install_rust_toolchain = false # default: true if invoked by rubygems
61+
end
62+
```
63+
64+
## Environment Variables
65+
66+
The `rb_sys` gem respects several environment variables that can modify its behavior:
67+
68+
| Environment Variable | Description |
69+
|---------------------|-------------|
70+
| `RB_SYS_CARGO_PROFILE` | Set the Cargo profile (i.e., `release` or `dev`) |
71+
| `RB_SYS_CARGO_FEATURES` | Comma-separated list of Cargo features to enable |
72+
| `RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN` | Force installation of a Rust toolchain |
73+
| `RUBY_STATIC` | Force static linking of libruby if set to `true` |
74+
| `LIBCLANG_PATH` | Path to libclang if it can't be found automatically |
75+
76+
## Tips and Tricks
77+
78+
- When using `rake-compiler` to build your gem, you can use the `RB_SYS_CARGO_PROFILE` environment variable to set the Cargo profile (i.e., `release` or `dev`).
79+
80+
- You can pass Cargo arguments to `rake-compiler` like so: `rake compile -- --verbose`
81+
82+
- It's possible to force an installation of a Rust toolchain by setting the `RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN` environment variable. This will install [rustup](https://rustup.rs/) and [cargo](https://crates.io/) in the build directory, so the end user does not have to have Rust pre-installed. Ideally, this should be a last resort, as it's better to already have the toolchain installed on your system.
83+
84+
## Troubleshooting
85+
86+
### Libclang Issues
87+
88+
If you see an error like this:
89+
90+
```
91+
thread 'main' panicked at 'Unable to find libclang: "couldn't find any valid shared libraries matching: \['libclang.so', 'libclang-*.so', 'libclang.so.*', 'libclang-*.so.*'\], set the `LIBCLANG_PATH` environment variable to a path where one of these files can be found (invalid: \[\])"'
92+
```
93+
94+
This means that bindgen is having issues finding a usable version of libclang. An easy way to fix this is to install the [`libclang` gem](https://github.com/oxidize-rb/libclang-rb), which will install a pre-built version of libclang for you. `rb_sys` will automatically detect this gem and use it.
95+
96+
```ruby
97+
# Gemfile
98+
gem "libclang", "~> 14.0.6"
99+
```
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# rb-sys-test-helpers
2+
3+
The `rb-sys-test-helpers` crate provides utilities for testing Ruby extensions from Rust. It makes it easy to run tests with a valid Ruby VM.
4+
5+
## Usage
6+
7+
Add this to your `Cargo.toml`:
8+
9+
```toml
10+
[dev-dependencies]
11+
rb-sys-env = { version = "0.1" }
12+
rb-sys-test-helpers = { version = "0.2" }
13+
```
14+
15+
Then, in your crate's `build.rs`:
16+
17+
```rust
18+
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
19+
let _ = rb_sys_env::activate()?;
20+
21+
Ok(())
22+
}
23+
```
24+
25+
Then, you can use the `ruby_test` attribute macro in your tests:
26+
27+
```rust
28+
#[cfg(test)]
29+
mod tests {
30+
use rb_sys_test_helpers::ruby_test;
31+
use rb_sys::{rb_num2fix, rb_int2big, FIXNUM_P};
32+
33+
#[ruby_test]
34+
fn test_something() {
35+
// Your test code here will have a valid Ruby VM (hint: this works with
36+
// the `magnus` crate, too!)
37+
//
38+
// ...
39+
40+
let int = unsafe { rb_num2fix(1) };
41+
let big = unsafe { rb_int2big(9999999) };
42+
43+
assert!(FIXNUM_P(int));
44+
assert!(!FIXNUM_P(big));
45+
}
46+
}
47+
```
48+
49+
## How It Works
50+
51+
The `ruby_test` macro sets up a Ruby VM before running your test and tears it down afterward. This allows you to interact with Ruby from your Rust code during tests without having to set up the VM yourself.
52+
53+
The test helpers are compatible with both `rb-sys` for low-level C API access and `magnus` for higher-level Ruby interactions.
54+
55+
## Common Testing Patterns
56+
57+
### Testing Value Conversions
58+
59+
```rust
60+
#[ruby_test]
61+
fn test_value_conversion() {
62+
use rb_sys::{rb_cObject, rb_funcall, rb_str_new_cstr, rb_iv_set};
63+
use std::ffi::CString;
64+
65+
unsafe {
66+
let obj = rb_cObject;
67+
let name = CString::new("test").unwrap();
68+
let value = rb_str_new_cstr(name.as_ptr());
69+
70+
rb_iv_set(obj, b"@name\0".as_ptr() as *const _, value);
71+
72+
let result = rb_funcall(obj, b"instance_variable_get\0".as_ptr() as *const _, 1,
73+
rb_str_new_cstr(b"@name\0".as_ptr() as *const _));
74+
75+
assert_eq!(value, result);
76+
}
77+
}
78+
```
79+
80+
### Testing with Magnus
81+
82+
```rust
83+
#[ruby_test]
84+
fn test_with_magnus() {
85+
use magnus::{Ruby, RString, Value};
86+
87+
let ruby = unsafe { Ruby::get().unwrap() };
88+
let string = RString::new(ruby, "Hello, world!").unwrap();
89+
90+
assert_eq!(string.to_string().unwrap(), "Hello, world!");
91+
}
92+
```
93+
94+
## Testing Multiple Ruby Versions
95+
96+
To test against multiple Ruby versions, you can use environment variables and CI configuration:
97+
98+
```yaml
99+
# .github/workflows/test.yml
100+
jobs:
101+
test:
102+
strategy:
103+
matrix:
104+
ruby: ['2.7', '3.0', '3.1', '3.2', '3.3']
105+
steps:
106+
- uses: actions/checkout@v4
107+
- uses: oxidize-rb/actions/setup-ruby-and-rust@v1
108+
with:
109+
ruby-version: ${{ matrix.ruby }}
110+
- run: cargo test
111+
```
112+
113+
Your tests will run against each Ruby version in the matrix, helping you ensure compatibility.
114+
115+
## Integration with Other Test Frameworks
116+
117+
The `ruby_test` attribute works with common Rust test frameworks like `proptest` and `quickcheck`:
118+
119+
```rust
120+
#[ruby_test]
121+
fn test_with_proptest() {
122+
use proptest::prelude::*;
123+
124+
proptest!(|(s in "[a-zA-Z0-9]*")| {
125+
let ruby = unsafe { Ruby::get().unwrap() };
126+
let ruby_string = RString::new(ruby, &s).unwrap();
127+
assert_eq!(ruby_string.to_string().unwrap(), s);
128+
});
129+
}
130+
```

book/src/build-process.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,17 @@ create_rust_makefile("my_gem/my_gem") do |config|
5555

5656
# Clean up target directory after installation to reduce gem size
5757
config.clean_after_install = true
58+
59+
# Force installation of Rust toolchain if not present
60+
config.force_install_rust_toolchain = "stable"
61+
62+
# Auto-install Rust toolchain during gem installation
63+
config.auto_install_rust_toolchain = true
5864
end
5965
```
6066

67+
For a complete reference of all available configuration options, see the [rb_sys Gem Configuration](./api-reference/rb-sys-gem-config.md) documentation.
68+
6169
## Environment Variables
6270

6371
Several environment variables affect the build process:

0 commit comments

Comments
 (0)