Giter VIP home page Giter VIP logo

magnus's Introduction

Magnus

High level Ruby bindings for Rust. Write Ruby extension gems in Rust, or call Ruby code from a Rust binary.

API Docs | GitHub | crates.io

Getting Started | Type Conversions | Safety | Compatibility

Examples

Defining Methods

Using Magnus, regular Rust functions can be bound to Ruby as methods with automatic type conversion. Callers passing the wrong arguments or incompatible types will get the same kind of ArgumentError or TypeError they are used to seeing from Ruby's built in methods.

Defining a function (with no Ruby self argument):

fn fib(n: usize) -> usize {
    match n {
        0 => 0,
        1 | 2 => 1,
        _ => fib(n - 1) + fib(n - 2),
    }
}

#[magnus::init]
fn init(ruby: &magnus::Ruby) -> Result<(), Error> {
    ruby.define_global_function("fib", magnus::function!(fib, 1));
    Ok(())
}

Defining a method (with a Ruby self argument):

fn is_blank(rb_self: String) -> bool {
    !rb_self.contains(|c: char| !c.is_whitespace())
}

#[magnus::init]
fn init(ruby: &magnus::Ruby) -> Result<(), Error> {
    // returns the existing class if already defined
    let class = ruby.define_class("String", ruby.class_object())?;
    // 0 as self doesn't count against the number of arguments
    class.define_method("blank?", magnus::method!(is_blank, 0))?;
    Ok(())
}

Calling Ruby Methods

Some Ruby methods have direct counterparts in Ruby's C API and therefore in Magnus. Ruby's Object#frozen? method is available as magnus::ReprValue::check_frozen, or Array#[] becomes magnus::RArray::aref.

Other Ruby methods that are defined only in Ruby must be called with magnus::ReprValue::funcall. All of Magnus' Ruby wrapper types implement the ReprValue trait, so funcall can be used on all of them.

let s: String = value.funcall("test", ())?; // 0 arguments
let x: bool = value.funcall("example", ("foo",))?; // 1 argument
let i: i64 = value.funcall("other", (42, false))?; // 2 arguments, etc

funcall will convert return types, returning Err(magnus::Error) if the type conversion fails or the method call raised an error. To skip type conversion make sure the return type is magnus::Value.

Wrapping Rust Types in Ruby Objects

Rust structs and enums can be wrapped in Ruby objects so they can be returned to Ruby.

Types can opt-in to this with the magnus::wrap macro (or by implementing magnus::TypedData). Whenever a compatible type is returned to Ruby it will be wrapped in the specified class, and whenever it is passed back to Rust it will be unwrapped to a reference.

use magnus::{function, method, prelude::*, Error, Ruby};

#[magnus::wrap(class = "Point")]
struct Point {
    x: isize,
    y: isize,
}

impl Point {
    fn new(x: isize, y: isize) -> Self {
        Self { x, y }
    }

    fn x(&self) -> isize {
        self.x
    }

    fn y(&self) -> isize {
        self.y
    }

    fn distance(&self, other: &Point) -> f64 {
        (((other.x - self.x).pow(2) + (other.y - self.y).pow(2)) as f64).sqrt()
    }
}

#[magnus::init]
fn init(ruby: &Ruby) -> Result<(), Error> {
    let class = ruby.define_class("Point", ruby.class_object())?;
    class.define_singleton_method("new", function!(Point::new, 2))?;
    class.define_method("x", method!(Point::x, 0))?;
    class.define_method("y", method!(Point::y, 0))?;
    class.define_method("distance", method!(Point::distance, 1))?;
    Ok(())
}

The newtype pattern and RefCell can be used if mutability is required:

struct Point {
    x: isize,
    y: isize,
}

#[magnus::wrap(class = "Point")]
struct MutPoint(std::cell::RefCell<Point>);

impl MutPoint {
    fn set_x(&self, i: isize) {
        self.0.borrow_mut().x = i;
    }
}

To allow wrapped types to be subclassed they must implement Default, and define and alloc func and an initialize method:

#[derive(Default)]
struct Point {
    x: isize,
    y: isize,
}

#[derive(Default)]
#[wrap(class = "Point")]
struct MutPoint(RefCell<Point>);

impl MutPoint {
    fn initialize(&self, x: isize, y: isize) {
        let mut this = self.0.borrow_mut();
        this.x = x;
        this.y = y;
    }
}

#[magnus::init]
fn init(ruby: &Ruby) -> Result<(), Error> {
    let class = ruby.define_class("Point", ruby.class_object()).unwrap();
    class.define_alloc_func::<MutPoint>();
    class.define_method("initialize", method!(MutPoint::initialize, 2))?;
    Ok(())
}

Getting Started

Writing an extension gem (calling Rust from Ruby)

Ruby extensions must be built as dynamic system libraries, this can be done by setting the crate-type attribute in your Cargo.toml.

Cargo.toml

[lib]
crate-type = ["cdylib"]

[dependencies]
magnus = "0.6"

When Ruby loads your extension it calls an 'init' function defined in your extension. In this function you will need to define your Ruby classes and bind Rust functions to Ruby methods. Use the #[magnus::init] attribute to mark your init function so it can be correctly exposed to Ruby.

src/lib.rs

use magnus::{function, Error, Ruby};

fn distance(a: (f64, f64), b: (f64, f64)) -> f64 {
    ((b.0 - a.0).powi(2) + (b.1 - a.1).powi(2)).sqrt()
}

#[magnus::init]
fn init(ruby: &Ruby) -> Result<(), Error> {
    ruby.define_global_function("distance", function!(distance, 2));
}

If you wish to package your extension as a Gem, we recommend using the rb_sys gem to build along with rake-compiler. These tools will automatically build your Rust extension as a dynamic library, and then package it as a gem.

Note: The newest version of rubygems does have beta support for compiling Rust, so in the future the rb_sys gem won't be necessary.

my_example_gem.gemspec

spec.extensions = ["ext/my_example_gem/extconf.rb"]

# needed until rubygems supports Rust support is out of beta
spec.add_dependency "rb_sys", "~> 0.9.39"

# only needed when developing or packaging your gem
spec.add_development_dependency "rake-compiler", "~> 1.2.0"

Then, we add an extconf.rb file to the ext directory. Ruby will execute this file during the compilation process, and it will generate a Makefile in the ext directory. See the rb_sys gem for more information.

ext/my_example_gem/extconf.rb

require "mkmf"
require "rb_sys/mkmf"

create_rust_makefile("my_example_gem/my_example_gem")

See the rust_blank example for examples if extconf.rb and Rakefile. Running rake compile will place the extension at lib/my_example_gem/my_example_gem.so (or .bundle on macOS), which you'd load from Ruby like so:

lib/my_example_gem.rb

require_relative "my_example_gem/my_example_gem"

For a more detailed example (including cross-compilation and more), see the rb-sys example project. Although the code in lib.rs does not feature magnus, but it will compile and run properly.

Embedding Ruby in Rust

To call Ruby from a Rust program, enable the embed feature:

Cargo.toml

[dependencies]
magnus = { version = "0.6", features = ["embed"] }

This enables linking to Ruby and gives access to the embed module. magnus::embed::init must be called before calling Ruby and the value it returns must not be dropped until you are done with Ruby. init can not be called more than once.

src/main.rs

use magnus::eval;

fn main() {
    magnus::Ruby::init(|ruby| {
        let val: f64 = eval!(ruby, "a + rand", a = 1)?;

        println!("{}", val);

        Ok(())
    }).unwrap();
}

Type Conversions

Magnus will automatically convert between Rust and Ruby types, including converting Ruby exceptions to Rust Results and vice versa.

These conversions follow the pattern set by Ruby's core and standard libraries, where many conversions will delegate to a #to_<type> method if the object is not of the requested type, but does implement the #to_<type> method.

Below are tables outlining many common conversions. See the Magnus api documentation for the full list of types.

Rust functions accepting values from Ruby

See magnus::TryConvert for more details.

Rust function argument accepted from Ruby
i8,i16,i32,i64,isize, magnus::Integer Integer, #to_int
u8,u16,u32,u64,usize Integer, #to_int
f32,f64, magnus::Float Float, Numeric
String, PathBuf, char, magnus::RString, bytes::Bytes*** String, #to_str
magnus::Symbol Symbol, #to_sym
bool any object
magnus::Range Range
magnus::Encoding, magnus::RbEncoding Encoding, encoding name as a string
Option<T> T or nil
(T, U), (T, U, V), etc [T, U], [T, U, V], etc, #to_ary
[T; N] [T], #to_ary
magnus::RArray Array, #to_ary
magnus::RHash Hash, #to_hash
std::time::SystemTime, magnus::Time Time
magnus::Value any object
Vec<T>* [T], #to_ary
HashMap<K, V>* {K => V}, #to_hash
&T, typed_data::Obj<T> where T: TypedData** instance of <T as TypedData>::class()

* when converting to Vec and HashMap the types of T/K,V must be native Rust types.

** see the wrap macro.

*** when the bytes feature is enabled

Rust returning / passing values to Ruby

See magnus::IntoValue for more details, plus magnus::method::ReturnValue and magnus::ArgList for some additional details.

returned from Rust / calling Ruby from Rust received in Ruby
i8,i16,i32,i64,isize Integer
u8,u16,u32,u64,usize Integer
f32, f64 Float
String, &str, char, &Path, PathBuf String
bool true/false
() nil
Range, RangeFrom, RangeTo, RangeInclusive Range
Option<T> T or nil
Result<T, magnus::Error> (return only) T or raises error
(T, U), (T, U, V), etc, [T; N], Vec<T> Array
HashMap<K, V> Hash
std::time::SystemTime Time
T, typed_data::Obj<T> where T: TypedData** instance of <T as TypedData>::class()

** see the wrap macro.

Conversions via Serde

Rust types can also be converted to Ruby, and vice versa, using Serde with the serde_magnus crate.

Manual Conversions

There may be cases where you want to bypass the automatic type conversions, to do this use the type magnus::Value and then manually convert or type check from there.

For example, if you wanted to ensure your function is always passed a UTF-8 encoded String so you can take a reference without allocating you could do the following:

fn example(ruby: &Ruby, val: magnus::Value) -> Result<(), magnus::Error> {
    // checks value is a String, does not call #to_str
    let r_string = RString::from_value(val)
        .ok_or_else(|| magnus::Error::new(ruby.exception_type_error(), "expected string"))?;
    // error on encodings that would otherwise need converting to utf-8
    if !r_string.is_utf8_compatible_encoding() {
        return Err(magnus::Error::new(
            ruby.exception_encoding_error(),
            "string must be utf-8",
        ));
    }
    // RString::as_str is unsafe as it's possible for Ruby to invalidate the
    // str as we hold a reference to it. The easiest way to ensure the &str
    // stays valid is to avoid any other calls to Ruby for the life of the
    // reference (the rest of the unsafe block).
    unsafe {
        let s = r_string.as_str()?;
        // ...
    }
    Ok(())
}

Safety

When using Magnus, in Rust code, Ruby objects must be kept on the stack. If objects are moved to the heap the Ruby GC can not reach them, and they may be garbage collected. This could lead to memory safety issues.

It is not possible to enforce this rule in Rust's type system or via the borrow checker, users of Magnus must maintain this rule manually.

An example of something that breaks this rule would be storing a Ruby object in a Rust heap allocated data structure, such as Vec, HashMap, or Box. This must be avoided at all costs.

While it would be possible to mark any functions that could expose this unsafty as unsafe, that would mean that almost every interaction with Ruby would be unsafe. This would leave no way to differentiate the really unsafe functions that need much more care to use.

Other than this, Magnus strives to match Rust's usual safety guaranties for users of the library. Magnus itself contains a large amount of code marked with the unsafe keyword, it is impossible to interact with Ruby's C-api without this, but users of Magnus should be able to do most things without needing to use unsafe.

Compatibility

Ruby versions 3.0, 3.1, 3.2, and 3.3 are fully supported.

Magnus currently works with, and is still tested against, Ruby 2.7, but as this version of the language is no longer supported by the Ruby developers it is not recommended and future support in Magnus is not guaranteed.

Ruby bindings will be generated at compile time, this may require libclang to be installed.

The Minimum supported Rust version is currently Rust 1.61.

Support for statically linking Ruby is provided via the lower-level rb-sys crate, and can be enabled by adding the following to your Cargo.toml:

# * should select the same version used by Magnus
rb-sys = { version = "*", default-features = false, features = ["ruby-static"] }

Cross-compilation is supported by rb-sys for the platforms listed here.

Magnus is not tested on 32 bit systems. Efforts are made to ensure it compiles. Patches are welcome.

Crates that work with Magnus

rb-sys

Magnus uses rb-sys to provide the low-level bindings to Ruby. The rb-sys feature enables the rb_sys module for advanced interoperability with rb-sys, allows you to access low-level Ruby APIs which Magnus does not expose.

serde_magnus

serde_magnus integrates Serde and Magnus for seamless serialisation and deserialisation of Rust to Ruby data structures and vice versa.

Users

  • halton a Ruby gem providing a highly optimised method for generating Halton sequences.

Please open a pull request if you'd like your project listed here.

Troubleshooting

Issues with static linking

If you encounter an error such as symbol not found in flat namespace '_rb_ext_ractor_safe' when embedding static Ruby, you will need to instruct Cargo not to strip code that it thinks is dead.

In you the same directory as your Cargo.toml file, create a .cargo/config.toml file with the following contents:

[build]
# Without this flag, when linking static libruby, the linker removes symbols
# (such as `_rb_ext_ractor_safe`) which it thinks are dead code... but they are
# not, and they need to be included for the `embed` feature to work with static
# Ruby.
rustflags = ["-C", "link-dead-code=on"]

Naming

Magnus is named after Magnus the Red a character from the Warhammer 40,000 universe. A sorcerer who believed he could tame the psychic energy of the Warp. Ultimately, his hubris lead to his fall to Chaos, but let's hope using this library turns out better for you.

License

This project is licensed under the MIT license, see LICENSE.

magnus's People

Contributors

adampetro avatar ankane avatar briankung avatar burke avatar cramt avatar dylanahsmith avatar eliias avatar georgeclaghorn avatar gjtorikian avatar gmalette avatar ianks avatar jbourassa avatar maaarcocr avatar matsadler avatar ms-jpq avatar vagab avatar y-yagi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

magnus's Issues

Ease of accepting `TypedData` arguments

So I'm running up against a scenario where I would like to accept some TypedData arguments, and experiencing some paint points. Take this example:

use magnus::{define_class, function, prelude::*, Error};

#[derive(Debug, Clone)]
#[magnus::wrap(class = "Point")]
struct Point {}

#[magnus::wrap(class = "Triangle")]
struct Triangle {
    a: Point,
    b: Point,
    c: Point,
}

impl Triangle {
    fn new(a: Point, b: Point, c: Point) -> Self {
        Self { a, b, c }
    }
}

#[magnus::init]
fn init() -> Result<(), Error> {
    define_class("MultiPoint", Default::default())?
        .define_singleton_method("new", function!(Triangle::new, 3))?;
    Ok(())
}

Will lead to this error currently:

error[E0599]: the method `call_handle_error` exists for struct `Function3<fn(Point, Point, Point) -> Triangle {Triangle::new}, Point, Point, Point, Triangle>`, but its trait bounds were not satisfied
  --> src/lib.rs:23:41
   |
5  | struct Point {}
   | ------------ doesn't satisfy `Point: TryConvert`
...
23 |         .define_singleton_method("new", function!(Triangle::new, 3))?;
   |                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^ method cannot be called on `Function3<fn(Point, Point, Point) -> Triangle {Triangle::new}, Point, Point, Point, Triangle>` due to unsatisfied trait bounds
   |
   = note: the following trait bounds were not satisfied:
           `Point: TryConvert`
note: the following trait must be implemented
  --> /Users/ianks/.cargo/git/checkouts/magnus-af51bb84edff177b/cc852bf/src/try_convert.rs:17:1
   |
17 | / pub trait TryConvert: Sized {
18 | |     /// Convert `val` into `Self`.
19 | |     fn try_convert(val: Value) -> Result<Self, Error>;
20 | | }
   | |_^
   = note: this error originates in the macro `function` (in Nightly builds, run with -Z macro-backtrace for more info)

The Issue

Currently, [TryConvert] is only implemented for &TypedData]try-convert. However, TypedData cannot be derived for generic types (i.e. you cannot specify the lifetime of each point in the triangle struct Triangle<'point>). So when taking the &Point arg by reference, you have to clone the point:

 impl Triangle {
-    fn new(a: Point, b: Point, c: Point) -> Self {
-        Self { a, b, c }
+    fn new(a: &Point, b: &Point, c: &Point) -> Self {
+        Self {
+            a: a.clone(),
+            b: b.clone(),
+            c: c.clone(),
+        }
     }
 }

Potential Solutions

Since the wrap macro is a bit hidden, it took me a bit to understand what the issue was, and I imagine this will be a pain point for others adopting magnus as well. Here are a couple of ideas I had for fixing it:

  1. Add a impl<T: TypedData> TryConvert for T which would effective do the cloning for you (easiest for the end user, easier to implement)
  2. Add ability to derive when using lifetimes with TypedData so users could specify that the &'point Point would live at least as long as the triangle (hardest for end user, hardest to implement. Also unsound if the user doesn't mark Point correctly...)

Curious to hear your thoughts, maybe there is a better way?

new release with the rb-sys upgrade please

I see that the patch to upgrade to rb-sys v0.9.77 has landed. Can we please get a new release so that downstream things can get this benefit ( be able to compile under newer clang ).

How to keep Rust object reference for related Ruby object

I have some Rust code I'd like to expose to Ruby, so I've set up a test project using magnus. I'd like to create an instance of a Rust object in a Rust function, and then re-use that instance in other Rust methods exposed to Ruby.

This is similar to your Point example from the readme: https://github.com/matsadler/magnus#wrapping-rust-types-in-ruby-objects

However, the difference is that I don't want this entire object serialized for Ruby access. Therefore I'm not using the magnus::wrap attribute from your example, and I get the corresponding the trait bound TypedData is not satisfied as expected in that case.

Do you have a recommendation for how to do this? Is there a way to exclude individual struct properties from type wrapping so that Rust references stay in Rust? Does this make sense?

Critical bug in `scan_args::get_kwargs`

Sorry for the bombastic title, but I think the severity of the bug I've encountered is pretty high.

Essentially, it seems that get_kwargs is not converting Values correctly. Given the following Ruby code:

MagnusKwargsBug::Selector.new(match_text_within: "foo")

Backed by the following arg parsing in Magnus:

    #[allow(clippy::let_unit_value)]
    fn scan_parse_args(args: &[Value]) -> Result<(Option<String>, Option<String>), Error> {
        let args = scan_args::scan_args(args)?;
        let _: () = args.required;
        let _: () = args.optional;
        let _: () = args.splat;
        let _: () = args.trailing;
        let _: () = args.block;

        let kw = scan_args::get_kwargs::<_, (), (Option<String>, Option<String>), ()>(
            args.keywords,
            &[],
            &["match_element", "match_text_within"],
        )?;
        let (match_element, match_text_within) = kw.optional;

        Ok((match_element, match_text_within))
    }

The following error occurs at runtime in the scan_args::get_kwargs function:

  1) Error:
TestMagnusKwargsBug#test_that_it_can_accept_kwargs:
TypeError: no implicit conversion of Float into String
    /Users/gjtorikian/Development/magnus_kwargs_bug/test/test_magnus_kwargs_bug.rb:7:in `new'
    /Users/gjtorikian/Development/magnus_kwargs_bug/test/test_magnus_kwargs_bug.rb:7:in `test_that_it_can_accept_kwargs'

I'm not sure why or how a Float is being interpreted, rather than the defined String.

Here's a reproducible use case: https://github.com/gjtorikian/magnus_kwargs_bug; after cloning the repo, run bundle exec rake compile test.

When `nil` is coerced into `RArray`, it becomes `[nil]`

Unlike #35, I don't have time to set up a test repro at the moment, but hopefully this example will suffice. Let me know if you'd like to see a demo program and I can craft one.

Basically, if I do this in Ruby:

SomeClass.new(var: nil)

And in Magnus, do:

        let args = scan_args::scan_args(args)?;
        let _: () = args.required;
        let _: () = args.optional;
        let _: () = args.splat;
        let _: () = args.trailing;
        let _: () = args.block;

        let kw = scan_args::get_kwargs::<
            _,
            (),
            (
                Option<RArray>,
            ),
            (),
        >(args.keywords, &[], &["var"])?;
        let (rb_var) = kw.optional;

According to rb_var.inspect(), this is [nil], not nil; and thus, a TypeError is never raised, although I believe it should.

Unable to create subclasses of wrapped objects in Ruby

First off, thanks for making this project. The documentation is excellent, and it's been a good introduction to Ruby's internals as someone who hasn't dealt with them in the past.

I'm running into some odd behavior when subclassing a TypedData Rust object in Ruby. If I have a Rust-based superclass, Parent, and a sublass class defined in Ruby, Child, the object returned by Child.new will be a Parent. Child#initialize is not executed.

Is this expected?

This can behavior be replicated using the complete_object example in this repo with this patch:

diff --git a/examples/complete_object/lib/temperature.rb b/examples/complete_object/lib/temperature.rb
index 22440be..a1ea42b 100644
--- a/examples/complete_object/lib/temperature.rb
+++ b/examples/complete_object/lib/temperature.rb
@@ -1,3 +1,14 @@
 # frozen_string_literal: true
 
 require_relative "temperature/temperature"
+
+class Foo < Temperature
+  def initialize(temp)
+    puts "Initializing Foo"
+    super(celsius: temp)
+  end
+
+  def bar
+    puts "Hello from Foo"
+  end
+end
diff --git a/examples/complete_object/test/temperature_test.rb b/examples/complete_object/test/temperature_test.rb
index ceb5216..307e84c 100644
--- a/examples/complete_object/test/temperature_test.rb
+++ b/examples/complete_object/test/temperature_test.rb
@@ -5,6 +5,12 @@ require_relative "../lib/temperature"
 
 class TemperatureTest < Test::Unit::TestCase
 
+  def test_foo
+    foo = Foo.new(celsius: 20)
+    puts "Class of foo: #{foo.class}"
+    foo.bar
+  end
+
   def test_new
     assert { Temperature.new(kelvin: 292.65).to_kelvin == 292.65 }
     assert { Temperature.new(celsius: 19.5).to_celsius == 19.5 }

Running this results in:

$ bundle exec rake
Started
..Class of foo: Temperature
E
===========================
Error: test_foo(TemperatureTest): NoMethodError: undefined method `bar' for Temperature { microkelvin: 293150000 }:Temperature
/magnus/examples/complete_object/test/temperature_test.rb:11:in `test_foo'
      8:   def test_foo
      9:     foo = Foo.new(celsius: 20)
     10:     puts "Class of foo: #{foo.class}"
  => 11:     foo.bar
     12:   end
     13:
     14:   def test_new
==========================

Magnus version: HEAD (a42fdbef8f0a9726d15e24ed904e3e6bf2015351)
Rust version: rustc 1.67.1 (d5a82bbd2 2023-02-07)
Ruby versions: tested with ruby 3.0.5p211 (2022-11-24 revision ba5cf0f7c5) [arm64-darwin22] and ruby 3.2.1 (2023-02-08 revision 31819e82c8) [arm64-darwin22]

User Story: Building a ruby binding for OpenDAL

Hello, I just wanted to let you know that you are doing a great job! This project is readlly great! ❤️

OpenDAL is a rust lib that empower users to access data freely, painlessly, and efficiently. And we are using magnus to build a ruby binding so that ruby users can be empowered too.

Our work is happening at apache/opendal#1734, welcome to check it out and give us some advice 😆

Magnus overhead compared to rb-sys

Background

I'm benchmarking different ways of creating a large nested hash in Ruby. In my benchmark I compared an implementation using raw rb-sys with magnus-based implementation. To my surprise the latter seems to be over 2x slower. Is this expected? Perhaps I'm doing something wrong?

Benchmark results:

Calculating -------------------------------------
          Plain Ruby    206.215  (± 1.9%) i/s -      1.045k in   5.069316s
         C extension    314.509  (± 2.5%) i/s -      1.581k in   5.030353s
    rb-sys extension    323.220  (± 3.1%) i/s -      1.632k in   5.054636s
    Magnus extension    115.455  (± 5.2%) i/s -    580.000  in   5.035127s

Comparison:
    rb-sys extension:      323.2 i/s
         C extension:      314.5 i/s - same-ish: difference falls within error
          Plain Ruby:      206.2 i/s - 1.57x  slower
    Magnus extension:      115.5 i/s - 2.80x  slower

Plain Ruby = 4.85 ms
C extension = 3.18 ms
rb-sys extension = 3.10 ms
Magnus extension = 8.68 ms

Code

The code is also available in this repo:

Ruby:

PAYLOAD = "ABC"*100

def build_tree(depth)
  if depth == 1
    return {label: PAYLOAD.dup , children: []}
  end
  return {label: PAYLOAD.dup, children: [build_tree(depth-1), build_tree(depth-1)]}
end

def build_big_tree
  build_tree(13)
end

magnus implementation:

static PAYLOAD: &str = "ABC(...)";

fn build_tree(depth: i32) -> RHash {
    let result = RHash::new();
    result.aset(Symbol::new("label"), PAYLOAD).unwrap();
    let children = RArray::new();
    if depth != 1 {
        children.push(build_tree(depth - 1)).unwrap();
        children.push(build_tree(depth - 1)).unwrap();
    }
    result.aset(Symbol::new("children"), children).unwrap();
    return result;
}

fn build_big_tree() -> RHash {
    return build_tree(13);
}

rb-sys implementation:

static PAYLOAD: &str = "ABC(...)";

unsafe fn build_tree(depth: i32) -> VALUE {
    let result = rb_hash_new();
    let children = rb_ary_new();
    if depth != 1 {
        rb_ary_push(children, build_tree(depth - 1));
        rb_ary_push(children, build_tree(depth - 1));
    }
    rb_hash_aset(
        result,
        rb_id2sym(LABEL_INTERN),
        rb_str_new(PAYLOAD.as_ptr() as *mut _, PAYLOAD.len() as _),
    );
    rb_hash_aset(result, rb_id2sym(CHILDREN_INTERN), children);
    return result;
}

unsafe extern "C" fn build_big_tree(_: VALUE) -> VALUE {
    return build_tree(13);
}

Notes

I thought this might have something to do with building Ruby symbols, but using string keys in the hash doesn't affect the result much.

Related discussion: oxidize-rb/rb-sys#314

Thoughts on this idea for a `WrappedStruct`?

One common pattern we keep running into is the need for one magnus wrapped struct to hold and reference another (especially in wasmtime-rb).

Doing this today is tedious, and not super intuitive for new users. You have to understand how magnus works under the hood to realize the solution -- which is to store a Value in your struct on rely on TryConvert.

Here's an example on what you have to do to keep a reference on a struct today:

Given we have a point:

// point.rs

#[magnus::wrap(class = "Point", free_immediatly)]
struct Point(f64, f64);

impl Point {
    fn new(a: f64, b: f64) -> Self {
        Self(a, b)
    }
}

Old

#[derive(TypedData)]
#[magnus(class = "Line", free_immediatly)]
struct Line(Value, Value);

impl Line {
    fn new(a: Value, b: Value) -> Self {
        Self { a, b }
    }

    fn length(&self) -> Result<f64, Error> {
        let a: &Point = self.a.try_convert()?;
        let b: &Point = self.b.try_convert()?;

        Ok(((a.0 - b.0).powi(2) + (a.1 - b.1).powi(2)).sqrt())
    }
}

impl DataTypeFunctions for Line {
    fn mark(&self) {
        gc::mark(&self.a);
        gc::mark(&self.b);
    }
}

unsafe impl Send for Line {}

New

Given this is such a common pattern, I'm proposing a lightweight solution for it. The main goals for it are to:

  1. Make it clear to anyone that reads the code that you are storing a magnus struct, instead of just Value
  2. Keep track of the struct type information so it's easy to use (not have to know to use try_convert)
  3. Make it as easy as possible to mark for GC, since you'll have a really bad time otherwise

For now, I'm calling this thing WrappedStruct<T>, but open to other names... and other improvements in general. Here is how it would look:

#[derive(TypedData)]
#[magnus(class = "Line", free_immediatly)]
struct Line {
    a: WrappedStruct<Point>, // instead of `Value`
    b: WrappedStruct<Point>
};

impl Line {
    fn new(a: WrappedStruct<Point>, b: WrappedStruct<Point>) -> Self {
        Self(a, b)
    }

    fn length(&self) -> Result<f64, Error> {
        let a = self.a.get()?;
        let b = self.b.get()?;

        Ok((a.0 - b.0).powi(2) + (a.1 - b.1).powi(2)).sqrt())
    }
}

impl DataTypeFunctions for Line {
    fn mark(&self) {
        self.a.mark();
        self.b.mark();
    }
}

Impl

It's just a small generic container for TypedData with a thin interface. In case you want to test it out, I created a gist with an implementation you can just drop into your app.

WDYT?

`FiberError` with `Enumerator` on Ruby < 3.0

I’m consistently getting a FiberError back from magnus::Enumerator::next on Ruby 2.6 and Ruby 2.7:

#<FiberError: fiber called across stack rewinding barrier>

See this serde-magnus CI run for an example.

Strange details:

  • Only on x86-64 Linux. I haven't been able to reproduce the issue on an Apple Silicon Mac.
  • Only for heterogeneous arrays. [1234, true, "Hello, world!"], not [123, 456, 789].

I suspect ruby/ruby#4606 fixed this in Ruby 3.0. The timing isn’t right. 🤷‍♂️

Not sure if there’s anything to be done about this. Wanted to flag just in case.

Enabling the "embed" feature causes `undefined reference to 'uncompress'` on Linux

Hi, I was playing around with embedding Ruby into an app and ran into an issue on Linux where cargo run fails during compilation due to the following error:

  = note: /usr/bin/ld: /home/stanko/Desktop/crazy_idea/target/debug/deps/libmagnus-b135cde287fe83b2.rlib(addr2line.o): in function `uncompress_debug_section':
          /tmp/ruby-build.20230729185323.429701.n14x1w/ruby-3.2.1/addr2line.c:1978: undefined reference to `uncompress'
          collect2: error: ld returned 1 exit status

  = note: some `extern` functions couldn't be found; some native libraries may need to be installed or have their path specified
  = note: use the `-l` flag to specify native libraries to link
  = note: use the `cargo:rustc-link-lib` directive to specify the native libraries to link with Cargo (see https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorustc-link-libkindname)

error: could not compile `crazy_idea` (bin "crazy_idea") due to previous error
Full error message
    
   Compiling rb-sys v0.9.79
   Compiling magnus v0.4.4
   Compiling crazy_idea v0.1.0 (/home/stanko/Desktop/crazy_idea)
error: linking with `cc` failed: exit status: 1
  |
  = note: LC_ALL="C" PATH="/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/bin:/home/stanko/.asdf/shims:/home/stanko/.asdf/bin:/home/stanko/.zplug/bin:/home/stanko/Scripts:/home/stanko/.local/bin:/home/stanko/.cargo/bin:/home/stanko/.asdf/installs/nodejs/18.4.0/.npm/bin:/home/stanko/.yarn/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/home/stanko/.dotnet/tools:/var/lib/flatpak/exports/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/usr/lib/rustup/bin" VSLANG="1033" "cc" "-m64" "/tmp/rustcK9plxZ/symbols.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.1bcjakjgm9ldupmt.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.1ce1q2hdt5npiiyc.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.1i03mepp0gy0iltj.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.1ju3mga88h5jupje.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.29liimunk6ish5gv.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.2bi0bewo5sa2zjrt.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.2ln80v3wgultoh6j.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.2lwyyuw8vl6pdqws.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.2p583jesbt90zwcv.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.2qge5aqi25533tee.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.390ezfwlgzo4ds4r.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.3pqawr07p4cf0gaq.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.3qe12wxzqt1qf8sx.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.3wv1hogw6jgmjm2w.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.45rgg1bqeny9zvh8.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.4cko94khyf0bxo2s.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.4lveha4kch5lu9uw.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.4pc1q72spt0llgni.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.52x8n6xt5cbi8kds.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.555hd5bjj0w715p.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.5etkk4i84htpwo6d.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.cry3vs17reeb6gd.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.dmxt9z31a8tbkds.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.okzjqw64ie2u9lq.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.pcrw6dhh0ky82xq.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.pjhohbzrqmsywjf.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.to7gdbol6zayxh0.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.zh09phod34uiaxz.rcgu.o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023.3tly0tfwqthht8y1.rcgu.o" "-Wl,--as-needed" "-L" "/home/stanko/Desktop/crazy_idea/target/debug/deps" "-L" "/home/stanko/Desktop/crazy_idea/ruby/lib" "-L" "/home/stanko/Desktop/crazy_idea/ruby/lib" "-L" "/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-Wl,-Bstatic" "/home/stanko/Desktop/crazy_idea/target/debug/deps/libmagnus-b135cde287fe83b2.rlib" "/home/stanko/Desktop/crazy_idea/target/debug/deps/librb_sys-7c352b5a8d806963.rlib" "/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-8389830094602f5a.rlib" "/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libpanic_unwind-41c1085b8c701d6f.rlib" "/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libobject-f733fcc57ce38b99.rlib" "/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libmemchr-6495ec9d4ce4f37d.rlib" "/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libaddr2line-1e3796360cca5b49.rlib" "/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libgimli-2e7f329b154436e1.rlib" "/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_demangle-1e1f5b8a84008aa8.rlib" "/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd_detect-cbcb223c64b13cf3.rlib" "/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libhashbrown-b40bc72e060a8196.rlib" "/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libminiz_oxide-1eb33ae9877d3c0f.rlib" "/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libadler-0335d894dd05bed7.rlib" "/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_alloc-076a893ead7e7ab5.rlib" "/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libunwind-2e924dd85b2e9d95.rlib" "/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcfg_if-7975ffb5e62386c4.rlib" "/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liblibc-285425b7cea12024.rlib" "/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liballoc-38694d775e998991.rlib" "/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_core-914eb40be05d8663.rlib" "/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-27094fcca7e14863.rlib" "/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcompiler_builtins-919e055b306699ae.rlib" "-Wl,-Bdynamic" "-lrt" "-lgmp" "-ldl" "-lcrypt" "-lpthread" "-lrt" "-lgmp" "-ldl" "-lcrypt" "-lpthread" "-lgcc_s" "-lutil" "-lrt" "-lpthread" "-lm" "-ldl" "-lc" "-Wl,--eh-frame-hdr" "-Wl,-z,noexecstack" "-L" "/home/stanko/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-o" "/home/stanko/Desktop/crazy_idea/target/debug/deps/crazy_idea-5d69157a5b428023" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs"
  = note: /usr/bin/ld: /home/stanko/Desktop/crazy_idea/target/debug/deps/libmagnus-b135cde287fe83b2.rlib(addr2line.o): in function `uncompress_debug_section':
          /tmp/ruby-build.20230729185323.429701.n14x1w/ruby-3.2.1/addr2line.c:1978: undefined reference to `uncompress'
          collect2: error: ld returned 1 exit status

= note: some extern functions couldn't be found; some native libraries may need to be installed or have their path specified
= note: use the -l flag to specify native libraries to link
= note: use the cargo:rustc-link-lib directive to specify the native libraries to link with Cargo (see https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorustc-link-libkindname)

error: could not compile crazy_idea (bin "crazy_idea") due to previous error

When I try to compile and run the same code on a Mac it compiles and runs just fine. This error only pops up, for me, on Linux.

I'm unsure if this is user error on my part or a Linux specific issue. Any help is much appreciated.

What I've already tried

At first this seemed to be an issue with my Linux install, so I tried compiling and running my app inside the rb-sys dev Docker container and got the same error.

Dockerfile, build and run instructions
    
# This is a modified version of the rb-sys devcontainer Dockerfile
FROM mcr.microsoft.com/vscode/devcontainers/rust:bullseye

ENV CHRUBY_VERSION="0.3.9"
BUILD_LIST="build-essential clang libclang-dev openssh-server"
DEBIAN_FRONTEND="noninteractive"
RUBY_VERSIONS="3.1.2 3.2.2"

USER root

RUN set -ex;
apt-get update &&
apt-get install -y --no-install-recommends $BUILD_LIST && \

postmodern gpg

cd /tmp;
wget https://raw.github.com/postmodern/postmodern.github.io/main/postmodern.asc;
gpg --import postmodern.asc;
rm postmodern.asc; \

Install chruby

mkdir /tmp/chruby-build;
cd /tmp/chruby-build;
wget -O chruby-$CHRUBY_VERSION.tar.gz https://github.com/postmodern/chruby/archive/v$CHRUBY_VERSION.tar.gz;
wget https://raw.github.com/postmodern/chruby/master/pkg/chruby-$CHRUBY_VERSION.tar.gz.asc;
gpg --verify chruby-$CHRUBY_VERSION.tar.gz.asc chruby-$CHRUBY_VERSION.tar.gz;
tar -xzvf chruby-$CHRUBY_VERSION.tar.gz;
cd chruby-$CHRUBY_VERSION/;
sudo bash -c "./scripts/setup.sh";
echo "source /usr/local/share/chruby/chruby.sh" >> "/etc/profile.d/10-ruby.sh";
echo "chruby 3.1" >> "/etc/profile.d/10-ruby.sh";
cd /; \

Install ruby-build

mkdir /tmp/ruby-build-build;
cd /tmp/ruby-build-build;
git clone https://github.com/rbenv/ruby-build.git;
PREFIX=/usr/local ./ruby-build/install.sh;
cd /; \

Install ruby versions

echo "====== BUILDING RUBIES ========";
echo 'gem: --no-document' >> $HOME/.gemrc;
for ver in $RUBY_VERSIONS; do
RUBY_CONFIGURE_OPTS="--disable-shared --disable-install-doc --disable-install-rdoc" ruby-build "$ver" "/opt/rubies/$ver";
done; \

Cleanup

apt-get clean &&
rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/* /chruby* /usr/local/src/ruby*;

To build the container, and execute cargo run inside it I ran the following:

    
docker build -t crazy_idea .
docker run --rm -it -v $PWD:/app bash
cd app
RUBY=/opt/rubies/3.2.2/bin/ruby cargo run
    
  

Adding a .cargo/config.toml file to the project's dir and instructing rustc to keep unused symbols didn't help.

Contents of the config.toml file
    
[build]
# Without this flag, when linking static libruby, the linker removes symbols
# (such as `_rb_ext_ractor_safe`) which it thinks are dead code... but they are
# not, and they need to be included for the `embed` feature to work with static
# Ruby.
rustflags = ["-C", "link-dead-code=on"]
    
  

Different versions of magnus - from version 0.4 to 0.6 - all produce the same error.

Project

There is just one file - src/main.rs - that contains the embedding example from the docs

use magnus::{embed, eval};

fn main() {
    println!("Hello, Rust world!");

    let _cleanup = unsafe { embed::init() };

    let val: f64 = eval!("a + rand", a = 1).unwrap();

    println!("{}", val);
}

this is the project's cargo.toml file

[package]
name = "crazy_idea"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
magnus = { version = "0.4", features = ["ruby-static", "embed"] }

Ruby is built using RUBY_CONFIGURE_OPTS="--disable-shared --disable-install-doc --disable-install-rdoc" ruby-build "3.2.2" "$HOME/Desktop/crazy_idea/ruby"

The project is compiled and ran using RUBY=$PWD/ruby/bin/ruby cargo run

Errors on `arm-linux`

Just documenting some errors I encountered on arm-linux. Mostly u32 <-> u64 things.

error[E0308]: mismatched types
    --> /home/runner/work/yrb/yrb/tmp/cargo-vendor/magnus/src/value.rs:2267:64
     |
2267 |             return Some(unsafe { Self::from_rb_value_unchecked(v.rotate_left(3) & !0x01 | 0x02) });
     |                                                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `u32`, found `u64`
     |
help: you can convert a `u64` to a `u32` and panic if the converted value doesn't fit
     |
2267 |             return Some(unsafe { Self::from_rb_value_unchecked((v.rotate_left(3) & !0x01 | 0x02).try_into().unwrap()) });
     |                                                                +                               +++++++++++++++++++++

error[E0308]: mismatched types
    --> /home/runner/work/yrb/yrb/tmp/cargo-vendor/magnus/src/value.rs:2310:41
     |
2310 |             let v = (2_u64.wrapping_sub(b63) | v & !0x03).rotate_right(3);
     |                                         ^^^ expected `u64`, found `u32`
     |
help: you can convert a `u32` to a `u64`
     |
2310 |             let v = (2_u64.wrapping_sub(b63.into()) | v & !0x03).rotate_right(3);
     |                                            +++++++

error[E0308]: mismatched types
    --> /home/runner/work/yrb/yrb/tmp/cargo-vendor/magnus/src/value.rs:2310:48
     |
2310 |             let v = (2_u64.wrapping_sub(b63) | v & !0x03).rotate_right(3);
     |                                                ^^^^^^^^^ expected `u64`, found `u32`

error[E0277]: no implementation for `u64 | u32`
    --> /home/runner/work/yrb/yrb/tmp/cargo-vendor/magnus/src/value.rs:2310:46
     |
2310 |             let v = (2_u64.wrapping_sub(b63) | v & !0x03).rotate_right(3);
     |                                              ^ no implementation for `u64 | u32`
     |
     = help: the trait `BitOr<u32>` is not implemented for `u64`
     = help: the following other types implement trait `BitOr<Rhs>`:
               <&'a i128 as BitOr<i128>>
               <&'a i16 as BitOr<i16>>
               <&'a i32 as BitOr<i32>>
               <&'a i64 as BitOr<i64>>
               <&'a i8 as BitOr<i8>>
               <&'a isize as BitOr<isize>>
               <&'a u128 as BitOr<u128>>
               <&'a u16 as BitOr<u16>>
             and 52 others

Is there a way to not statically link under windows?

if std::env::var_os("CARGO_FEATURE_EMBED").is_some() || cfg!(windows) {

Hey there,

I am trying to just get one of the ruby <-> rust crates working under windows and it really doesn't look like its possible?

I was wondering if you had also bashed your face against the wall too on this haha.

My use case is building a gem that would compile everything ahead of time, and it seems to work all all platforms except windows, which is a big pain in my bum rn.

btw, very nice code! 👍

serde_magnus discontinued?

not totally related to magnus but idk any better place to ask about this
refering to #47

i tried searching for forks but can't find any , does anyone know what happened?

Instructions for Examples

I'm having some troubles building the examples - getting some weird errors out of Cargo. Am not a Rust user myself, and whilst confident with Ruby, never really had to use rake or bundler or package a gem before.

Please could you provide a copy-pastable example of how to build one of the examples in the example folder, so that it's possible to verify that I/someone else is doing everything corectly?

Thanks

Working with nested structs

Hi!
I'm exploring the idea of offloading to a Rust native extension the serialization work in a Ruby application.
Everything works alright for struct whose fields are primitive values and/or "standard" types (e.g. String). I start struggling when it comes to nested structs, e.g.:

#[magnus::wrap(class = "WireRepresentation")]
#[derive(serde::Serialize)]
struct WireRepresentation {
    address: Address,
    name: String
}

impl WireRepresentation {
    pub fn new(address: &Address, name: String) -> Self {
        Self {
            address: address.to_owned(),
            name
        }
    }
    pub fn to_json_str(&self) -> String {
        serde_json::to_string(&self).unwrap()
    }
}

#[magnus::wrap(class = "Address")]
#[derive(serde::Serialize, Clone)]
struct Address {
    street: String,
    number: u64 
}

// [...]

The above compiles, but it performs an address.to_owned() invocation that I'd like to avoid. I was nudged in this direction by the compiler—my first draft took Address by value in WireRepresentation::new, but it resulted in the following error message:

error[E0277]: the trait bound `Address: TryConvert` is not satisfied
    --> src/lib.rs:42:41
     |
42   |     wire.define_singleton_method("new", function!(WireRepresentation::new, 2))?;
     |                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TryConvert` is not implemented for `Address`
     |
     = help: the following other types implement trait `TryConvert`:
               &T
               (T0, T1)
               (T0, T1, T2)
               (T0, T1, T2, T3)
               (T0, T1, T2, T3, T4)
               (T0, T1, T2, T3, T4, T5)
               (T0, T1, T2, T3, T4, T5, T6)
               (T0, T1, T2, T3, T4, T5, T6, T7)

Am I going in the right direction or is there a better way to handle this situation that doesn't require cloning the entire nested struct?

Ruby claims gem extensions aren't built

First and foremost, thank you for all of your hard work on this project. Being able to write Ruby extensions in Rust with minimal fanfare is enticing; I'm eager to use this library!

I've been trying to build a Ruby gem that leverages magnus, and despite being able to build the gem itself without issue, when trying to consume it from another Ruby project, it complains that the native extensions aren't built:

Ignoring rust_blank-0.1.0 because its extensions are not built. Try: gem pristine rust_blank --version 0.1.0

To simplify things as much as possible, I've taken the rust_blank example from this repo and extended it with the bare minimum that highlights what I'm trying to do. From a clean slate within that repo, I've done the following:

  1. Built the gem using rake in the root directory (provided you have the rake-compiler gem installed, this builds and tests the gem successfully)
  2. Navigated into the app/ subdirectory and run bundle, to install the path-referenced gem (runs without issue)
  3. Run bundle exec irb to get an interactive shell to try the gem (loads, but displays the error above)

Can you help shed light on what I'm doing wrong here? Unlikely to be an issue, but for completeness' sake, I'm running Ruby v3.1.3p185 and Rust v1.67.1.

Make `msrv` 1.57?

Recently, bindgen bumped their minimum supported Rust version to 1.57. With this change, we are unfortunately locked out of updating the package. Which brings the question, what should rb-sys set at MSRV as a policy?

Some random sampling I did

OS Package Manager Versions

  • Ubuntu (w/ updates) == 1.61
  • Debian stretch + buster == 1.41
  • Debian bullseye == 1.48
  • Debian bookwork + sid == 1.63
  • CentOS 8 == 1.54
  • Fedora 37 == 1.64
  • Fedora 35 == 1.54
  • Alpine 3.17 (2021-08-11 20:04:20) == 1.52
  • Alpine 3.16 (2022-05-08 03:20:43) == 1.60.0-r2

Common crates

1.56.0 ┆ rayon, once_cell, indexmap, rayon-core, hashbrowns
1.57.0 ┆ bindgen

My general feeling ATM

The best way to distribute Rust gems is to ship precompiled binaries. This avoids the need for installing a Rust toolchain on the deployment machine and makes shipping code faster and more reliable. Most (if not all?) magnus gems I know of are using this strategy.

On top of that, rb-sys will (by default) install a Rust toolchain if none is found on the machine when building a gem. This allows us to keep a clean build environment and clean up any artifacts when we're done to avoid cache and image bloat.

With all of this in mind, I think supporting 1.57 is a reasonable choice for now. WDYT?

Regression on aarch64-linux

Report

#18 fixed a formatting issue, but seems to result in failed builds on (at least) aarch64-linux.

The action as reference https://github.com/y-crdt/yrb/actions/runs/3182123819/jobs/5187676906

I am not sure if the solution is to just cast to u8 instead of i8, but I assume pointers are always unsigned integers. Related PR #22

error[E0308]: mismatched types
   --> /home/runner/work/yrb/yrb/tmp/cargo-vendor/magnus/src/error.rs:272:52
    |
272 |             unsafe { rb_raise(class.as_rb_value(), FMT_S.as_ptr() as *const i8, msg.as_ptr()) }
    |                                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `u8`, found `i8`
    |
    = note: expected raw pointer `*const u8`
               found raw pointer `*const i8`

Feature request: implicit init function

Hi @matsadler ,

Thanks for creating this awesome crate.

When I was writing native extensions for NodeJS with Rust, I had used a crate called nap-rs. Its API design is quite impressive. I thought it might be of some reference value to you. If mangus:init could be implicit or could be defined in separate mods, it could be a big DX boost.

Sorry, this seems like a topic that should be posted in "discussions", but since this repo doesn't have discussions turned on, I had to create an issue.

Sidekiq not handling signals?

We're trying to debug an issue where our sidekiq workers (running code which is built with magnus) are not shutting down properly. Based on the stack, it looks like they never reacted to a TERM signal.

I've noticed this comment/function which leads me to believe that we may be blocking the ability for ruby to handle interrupts during the call.

magnus/src/thread.rs

Lines 377 to 396 in a1e98dd

/// Check for, and run, pending interrupts.
///
/// While Ruby is running a native extension function (such as one written
/// in Rust with Magnus) it can't process interrupts (e.g. signals or
/// `Thread#raise` called from another thread). Periodically calling this
/// function in any long running function will check for *and run* any
/// queued interrupts. This will allow your long running function to be
/// interrupted with say ctrl-c or `Timeout::timeout`.
///
/// If any interrupt raises an error it will be returned as `Err`.
///
/// Calling this function may execute code on another thread.
pub fn thread_check_ints(&self) -> Result<(), Error> {
protect(|| {
unsafe { rb_thread_check_ints() };
self.qnil()
})?;
Ok(())
}
}

However, when we tried to call the above function, it was private! I'm not sure what we should do in this case or if this is a known issue?

Feature Request: Add default type parameters to scan_args

Using magnus::scan_args::scan_args right now is pretty annoying, because you need to manually fill out all of the type parameters. It'd be really nice if all of the type parameters defaulted to () though! (Like the hasher on HashMap)

Question: raise custom exception

Hi @matsadler, this is a great project! Apologies for using issues to ask a question but discussions are not enabled. My experience with rust is minimal and magnus has helped me a lot to write my extension. What I haven't managed to do is raise a custom error. Can you please provide an example how it can be done? Thank you!

How well does the embed feature work?

There's a project called mkxp that is a open source implementation of RGSS. (the engine used by early RPG Maker) RGSS embeds Ruby, so mkxp needs to do the same. This has historically been a sore spot in mkxp, with Ruby often causing crashes, segfaults, and other weird behavior because it's been embedded. Generally people compile a custom build of Ruby to ensure everything works, and even then, it's still pretty messy.

Does Magnus behave the same way? Is there anything I need to be concerned about?

Build is broken when trying to use Bytes feature

Hi, we are trying to use Magnus with the Bytes feature, and it appears 0.6.1 is broken on crates.io

jwilm@cartella  ~/code/magnus  cargo build --features bytes
  Downloaded bytes v1.4.0
  Downloaded 1 crate (58.1 KB) in 0.16s
   Compiling bytes v1.4.0
   Compiling magnus v0.6.1 (/home/jwilm/code/magnus)
error[E0277]: the trait bound `value::Value: From<r_string::RString>` is not satisfied
    --> src/r_string.rs:1747:46
     |
1747 |         handle.str_from_slice(self.as_ref()).into()
     |                                              ^^^^ the trait `From<r_string::RString>` is not implemented for `value::Value`
     |
     = note: required for `r_string::RString` to implement `Into<value::Value>`

jwilm@cartella  ~/code/magnus  rustc -V
rustc 1.72.0 (5680fa18f 2023-08-23)

How does Magnus, Rutie, uniffi-rs differ

I am on the search for tools to replace some ruby code with rust. (after mangling around with an external golang based service and all the mess those micro-service-approaches bring up)
So i came across different how-tos like writing rust gem from scratch, how to use rust with ruby and i am bit puzzled about which way to go, also since i am learning rust along the way.

I understand that one should not pass arbitrary ruby objects into the rust-black box, but instead keep the interface small and clean. Besides the memory problems that can arise, this also to reduces the amount of translation-methods. With those, comes my question are there any advantages of using one or the other lib? They all seem to abstract the C-level wrapping.
After taking a dive into uniffi-rs and generating one of their examples, their approach to build ruby code based on templates and a general udf description seems pretty slick.

In the second mentioned article above the author talks about running rust outside of the ruby gvl, how does magnus deal with such?

I hope my questions are not to dummy, but there are quite some moving parts to figure out. A general info about the pros & cons of each approach could really help.

a possible solution for unlocking the GVL

Hi! I've seen in past issues that possibly allowing the GVL to be unlocked for rust functions would be something that magnus may do in the future. I've recently prototyped a way to do so in https://github.com/Maaarcocr/lucchetto

I was wondering if there would be any interest in mainlining that approach here, as I think it can be pretty useful to be able to decide if you want to hold the gvl lock or not.

I know that there are some safety concerns around unlocking the GVL and for that I made a small marker trait which is implemented for types that I deem to be safe to be passed in/out of functions that do not hold the GVL. Now this does not entirely disallow calling into ruby, but it's a nice first step.

For now it only works for free functions, as they are easier to make safe.

Anyway, I would be happy to scratch that repo and port the code here if there is interest! I think it works nicely.

I also recognise that the code is very young and it makes use of unsafe quite a bit, but I'm sure with some additional time spent on it we could make sure it's all good.

RHash.lookup for bool value

So, I have an option with default value true. As bool can be converted from any Value, I assumed nil is converted into Some(false), but it's converted into None:

3.0.0 :001 > puts Xml2Json::Json.build("<root><a>1</a><b>2</b></root>")
{"root":{"a":["1"],"b":["2"]}}
 => nil 
3.0.0 :002 > puts Xml2Json::Json.build("<root><a>1</a><b>2</b></root>", explicit_array: false)
{"root":{"a":"1","b":"2"}}
 => nil 
3.0.0 :003 > puts Xml2Json::Json.build("<root><a>1</a><b>2</b></root>", explicit_array: true)
{"root":{"a":["1"],"b":["2"]}}
 => nil 
3.0.0 :004 > puts Xml2Json::Json.build("<root><a>1</a><b>2</b></root>", explicit_array: 1)
{"root":{"a":["1"],"b":["2"]}}
 => nil 
3.0.0 :005 > puts Xml2Json::Json.build("<root><a>1</a><b>2</b></root>", explicit_array: nil)
{"root":{"a":["1"],"b":["2"]}}
 => nil

I assume it's because of how TryConvert for Option<T> is implemented. Not sure if scan_args can help here, it's not implemented for such a big number of kwargs.

So, is there any way to work with opts like it would be in Ruby?

def build(opts = {})
# or def build(**opts)
  ...
  if opts[:explicit_root]
    ...
  end
end

I see that aref is better here because it takes default value/proc into account, and that's exactly how #[] is implemented, but it doesn't solve conversion problem. And fetch is not an option because I need to differentiate conversion errors and key-not-found cases. So, the only option - applicable for any type - here is to use get and try_convert separately, right? But get doesn't care about defaults, and aref::<_, Option<Value>> is also not, uh, an option - it converts nil to None

Automatic `frozen?` check

Not sure if it's possible to implement, but it would be a nice feature to automatically perform check_frozen inside method when wrapped function takes mut rb_self.

Ruby Inheritance

Hey @matsadler, hope your year is off to a good start.

I was wondering if you've given any thought to how to do Ruby inheritance (recently came across the need for this with Tokenizers). For instance, in the code below, it'd be nice to be able to have a Shape class with Circle and Rectangle subclasses, and support passing those subclasses to functions that accept Shape as an argument.

enum Shape {
    Circle { r: f32 },
    Rectangle { x: f32, y: f32 },
}

impl Shape {
    pub fn area(&self) -> f32 {
        match self {
            Shape::Circle { r } => std::f32::consts::PI * r * r,
            Shape::Rectangle { x, y } => x * y,
        }
    }
}

fn print_area(s: &Shape) {
    println!("{}", s.area());
}

fn main() {
    let a = Shape::Circle { r: 1.0 };
    let b = Shape::Rectangle { x: 2.0, y: 3.0 };
    print_area(&a);
    print_area(&b);
}

Edit: Tokenizers example if it's helpful (the new method on subclasses currently returns the parent class)

Ensure a type is `Send+Sync` when `frozen_shareable` flag is used

Say we wrap a struct like so:

#[magnus::wrap(class = "Engine", frozen_shareable)]
pub struct Engine {
    ...
    timer_task: std::cell::RefCell<...>,
}

frozen_shareable will allow to share the Ruby instances of this class between native threads using Ractors. But the struct is not Send nor Sync. So it's not safe.

See bytecodealliance/wasmtime-rb#161.

Could we require the struct to be Send+Sync when frozen_shareable flag is used?

Embedded Ruby and require_relative

I am attempting to embed ruby as a scripting application for this project. I am magnus::eval<>-ing a file I read to string, and attempting to require one of my files containing a custom class in rust. My file structure looks like this:
image
and test.rb looks like:

require_relative 'src/core/position'

class Test
  def initialize; end;
  def run
    puts "Hello World!"
  end
end

Test.new.run
puts Position.new(1, 2, 3).to_s

But it says it cannot find the file. Am I misunderstanding how to do this? Is magnus the right library to use for this task, or should I use something like rtie or artichoke instead?
Also, some other questions:

  1. How do I magnus::init multiple classes?
  2. Would a user of this app need ruby installed on the system? I imagine so, no? Would I have to embed a runtime like artichoke?

Building on aarch64-apple-darwin fails with lots of undefined symbols

I just started working on porting my rust/ruby gem from using rutie to magnus. After commenting out most of the gem, then porting a single, simple class, I went to build to for the first time and got this:

error: linking with `cc` failed: exit status: 1
  |
  = note: LC_ALL="C" PATH="/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/bin:/Users/steve.loveless/.gem/ruby/3.2.2/bin:/Users/steve.loveless/.rubies/ruby-3.2.2/lib/ruby/gems/3.2.0/bin:/Users/steve.loveless/.rubies/ruby-3.2.2/bin:/opt/homebrew/opt/binutils/bin:/usr/local/bin:/Users/steve.loveless/Library/pnpm:/Users/steve.loveless/.rd/bin:/Users/steve.loveless/Library/Caches/zr/zsh-users/zsh-autosuggestions.git:/Users/steve.loveless/Library/Caches/zr/ohmyzsh/ohmyzsh.git/plugins/colored-man-pages:/Users/steve.loveless/Library/Caches/fnm_multishells/7824_1684529995315/bin:/Users/steve.loveless/.pyenv/shims:/opt/homebrew/bin:/opt/homebrew/sbin:/Users/steve.loveless/Library/Caches/fnm_multishells/7765_1684529995166/bin:/Users/steve.loveless/.pyenv/shims:/opt/homebrew/bin:/opt/homebrew/sbin:/opt/homebrew/opt/binutils/bin:/usr/local/bin:/Users/steve.loveless/Library/pnpm:/Users/steve.loveless/.rd/bin:/Users/steve.loveless/Library/Caches/zr/zsh-users/zsh-autosuggestions.git:/Users/steve.loveless/Library/Caches/zr/ohmyzsh/ohmyzsh.git/plugins/colored-man-pages:/Users/steve.loveless/Library/Caches/fnm_multishells/1086_1683564348280/bin:/Users/steve.loveless/.pyenv/shims:/Users/steve.loveless/.cargo/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/steve.loveless/.local/share/nvim/mason/bin:/Users/steve.loveless/Library/Android/sdk/emulator:/Users/steve.loveless/Library/Android/sdk/platform-tools:/opt/homebrew/opt/llvm/bin:/opt/homebrew/opt/fzf/bin:/Users/steve.loveless/.local/share/nvim/mason/bin:/Users/steve.loveless/Library/Android/sdk/emulator:/Users/steve.loveless/Library/Android/sdk/platform-tools:/opt/homebrew/opt/llvm/bin:/Users/steve.loveless/.local/share/nvim/mason/bin:/Users/steve.loveless/Library/Android/sdk/emulator:/Users/steve.loveless/Library/Android/sdk/platform-tools:/opt/homebrew/opt/llvm/bin:/opt/homebrew/opt/fzf/bin" VSLANG="1033" ZERO_AR_DATE="1" "cc" "-Wl,-exported_symbols_list,/var/folders/k9/xm7wlry90z3fjwl909bs1bb80000gp/T/rustcMYwsAb/list" "-arch" "arm64" "/var/folders/k9/xm7wlry90z3fjwl909bs1bb80000gp/T/rustcMYwsAb/symbols.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.112dysivh8oqeifh.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.136f034i6wm2v2ks.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.1hycbp1ngro94huo.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.1itfhxnh3qvjfclf.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.1miwwh65r45lu9zz.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.1oiwhglafl1pyfhs.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.1qkcndyo58nnh4gu.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.1ybxt5f3hsflda0u.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.1ymcwg8odhlhppex.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.1zrjkw2itul5knn.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.23olsgk10zdhpsyu.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.28r148pp9n76ywxk.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.2cv7ygq2n1dlqt1r.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.2du73l54denacgww.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.2f53w00ti8g0jlfq.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.2n9ym39p0ca39d1n.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.2yz7fqgtknrjxa97.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.2zn27yz3z0ws6g7q.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.35nwne9xpw7hmck7.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.3a2ad7rn2u1qqpbu.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.3apscnsb5c8ftz9m.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.3c6xgrdp527m62gr.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.3d8733ibqdgspxko.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.3iozay4oh9az333d.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.3repth6vq92a4lad.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.3s1cah6pbnar7dra.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.3w9kblygljzy5mdl.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.3wfkhxhc1stuziu.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.40pbo13mk5bkqmdw.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.4d0v6y1fcpjjnz9m.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.4ge2bpf4fy4jwjat.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.4k0syloh7r4zqimm.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.4k5477iw0y87q6cx.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.4o29byttvllr3ye6.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.4q1a32owkkfwz9o5.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.4zdih6luuebb3zei.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.5674dgi1r0jucnxx.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.5cv73fcxk142g1b6.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.5dt47s1quftdt2zv.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.5ri9j6x2uqwxddi.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.796w0fli2ypo0cx.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.9jj55dekl99q6pb.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.a2zsaj2kb18mrj9.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.b48654uverfquss.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.drnukdtro0kgo65.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.f61688arcwdmvf7.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.mhvjd55zujdvy29.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.qz2n69l97l7efbf.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.rbkm4h59vsj4j9u.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.uz1a4f98hyqu83j.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.vwu3lym6p7f9m8b.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.ydhn14iwmx26lhg.rcgu.o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/agrian_units_rb.4cqdj38e0z5cdc47.rcgu.o" "-L" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps" "-L" "/opt/homebrew/opt/[email protected]/lib" "-L" "/opt/homebrew/opt/readline/lib" "-L" "/opt/homebrew/opt/libyaml/lib" "-L" "/opt/homebrew/opt/gdbm/lib" "-L" "/opt/homebrew/opt/[email protected]/lib" "-L" "/opt/homebrew/opt/readline/lib" "-L" "/opt/homebrew/opt/libyaml/lib" "-L" "/opt/homebrew/opt/gdbm/lib" "-L" "/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/libheck-eb382742d46fdcd5.rlib" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/libagrian_units-4a32d68c62631c76.rlib" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/libnum_rational-7a5efd3c9c157f59.rlib" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/libnum_bigint-aa885302a37e6206.rlib" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/libnum_integer-4663a960d84b7d13.rlib" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/libwise_units-bd67c54e5b56497b.rlib" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/libpest-fe642600ed72a788.rlib" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/libucd_trie-08f2be16cdcd1d5b.rlib" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/libthiserror-5bdd8bb5c4879a3c.rlib" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/libapprox-410bf8e23ad729ab.rlib" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/libnum_traits-71ede2436e19bb43.rlib" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/libagrian_units_i18n-db6cffe0c4e01479.rlib" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/liblanguage_tags-691e8db4117f7758.rlib" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/libmagnus-750f48931a72c806.rlib" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/librb_sys-8cf493397c2d3c26.rlib" "/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libstd-bbb34fdc76849e75.rlib" "/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libpanic_unwind-e41c824887d8159b.rlib" "/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libobject-b09606eb0dd7294b.rlib" "/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libmemchr-76750a67d9c865d8.rlib" "/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libaddr2line-cb09c50a4870b1ca.rlib" "/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libgimli-a32904124371a559.rlib" "/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/librustc_demangle-980bd38e7c5463c1.rlib" "/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libstd_detect-0ae7d64836b89372.rlib" "/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libhashbrown-49c3adda225456f2.rlib" "/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libminiz_oxide-bea0fc5e745e2485.rlib" "/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libadler-ffcdcd42dedf62f3.rlib" "/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/librustc_std_workspace_alloc-91dade5af3113381.rlib" "/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libunwind-61b8a3d45b3b24c0.rlib" "/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libcfg_if-cd1f01f097a5016f.rlib" "/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/liblibc-0fc6bb0e7822d4e1.rlib" "/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/liballoc-1e87ca26633c74ac.rlib" "/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/librustc_std_workspace_core-e5603c6d12f5e46b.rlib" "/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libcore-94322c45aa090f07.rlib" "/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libcompiler_builtins-90099c6af476d811.rlib" "-lSystem" "-lc" "-lm" "-L" "/Users/steve.loveless/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib" "-o" "/Users/steve.loveless/Development/agrian/agrian_units_rb/target/debug/deps/libagrian_units_rb.dylib" "-dynamiclib" "-Wl,-dylib" "-nodefaultlibs"
  = note: Undefined symbols for architecture arm64:
            "_rb_any_to_s", referenced from:
                magnus::value::Value::to_s_infallible::h2cc6204d87fcfee3 in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.11.rcgu.o)
                magnus::value::Value::inspect::_$u7b$$u7b$closure$u7d$$u7d$::hcc399f7e6deb3e9a in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.11.rcgu.o)
            "_rb_ary_clear", referenced from:
                magnus::r_array::RArray::clear::_$u7b$$u7b$closure$u7d$$u7d$::h43bd334d7f930487 in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.12.rcgu.o)
            "_rb_ary_cmp", referenced from:
                magnus::r_array::RArray::cmp::_$u7b$$u7b$closure$u7d$$u7d$::h22550d8b9a922821 in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.12.rcgu.o)
            "_rb_ary_concat", referenced from:
                magnus::r_array::RArray::concat::_$u7b$$u7b$closure$u7d$$u7d$::hfc0c0f82bd96ffbc in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.12.rcgu.o)
            "_rb_ary_new", referenced from:
                magnus::r_array::_$LT$impl$u20$magnus..ruby_handle..RubyHandle$GT$::ary_new::hce83762b04fcb4cc in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.14.rcgu.o)
            "_rb_ary_new_capa", referenced from:
                magnus::r_array::_$LT$impl$u20$magnus..ruby_handle..RubyHandle$GT$::ary_new_capa::h40c1c135b461fa93 in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.14.rcgu.o)
            "_rb_ary_plus", referenced from:
                magnus::r_array::RArray::plus::hb40f7d2079f576f3 in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.12.rcgu.o)
            "_rb_ary_replace", referenced from:
                magnus::r_array::RArray::replace::_$u7b$$u7b$closure$u7d$$u7d$::h8cb108037e63c540 in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.12.rcgu.o)
            "_rb_ary_resize", referenced from:
                magnus::r_array::RArray::resize::_$u7b$$u7b$closure$u7d$$u7d$::hb8227446bb425c83 in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.12.rcgu.o)
            "_rb_ary_reverse", referenced from:
                magnus::r_array::RArray::reverse::_$u7b$$u7b$closure$u7d$$u7d$::ha02dce709551c6e9 in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.12.rcgu.o)
            "_rb_ary_rotate", referenced from:
                magnus::r_array::RArray::rotate::_$u7b$$u7b$closure$u7d$$u7d$::h4a99c18d95c9fc8e in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.12.rcgu.o)
            "_rb_ary_shared_with_p", referenced from:
                magnus::r_array::RArray::is_shared::h2141ac2a19262f6c in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.12.rcgu.o)
            "_rb_ary_sort_bang", referenced from:
                magnus::r_array::RArray::sort::_$u7b$$u7b$closure$u7d$$u7d$::h99324abc15397668 in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.12.rcgu.o)
            "_rb_ary_subseq", referenced from:
                magnus::r_array::RArray::dup::h47c851cc422342f3 in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.12.rcgu.o)
                magnus::r_array::RArray::subseq::h38eff0cb31435a25 in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.12.rcgu.o)
            "_rb_ary_to_ary", referenced from:
                magnus::r_array::RArray::to_ary::_$u7b$$u7b$closure$u7d$$u7d$::hb1bd064f77a8b0e6 in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.12.rcgu.o)
            "_rb_ascii8bit_encindex", referenced from:
                magnus::encoding::_$LT$impl$u20$magnus..ruby_handle..RubyHandle$GT$::ascii8bit_encindex::h14540f1370bbefc0 in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.14.rcgu.o)
            "_rb_ascii8bit_encoding", referenced from:
                magnus::encoding::_$LT$impl$u20$magnus..ruby_handle..RubyHandle$GT$::ascii8bit_encoding::h1fa3d97b007dbc68 in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.14.rcgu.o)
            "_rb_backref_get", referenced from:
                magnus::_$LT$impl$u20$magnus..ruby_handle..RubyHandle$GT$::backref_get::h501eb5ab0df65e9a in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.14.rcgu.o)
            "_rb_block_given_p", referenced from:
                magnus::block::_$LT$impl$u20$magnus..ruby_handle..RubyHandle$GT$::block_given::h2510ed9807178241 in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.14.rcgu.o)
            "_rb_block_proc", referenced from:
                931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.8.rcgu.o)
                

**snip out lots of similar backtrace**


magnus::value::_$LT$impl$u20$magnus..ruby_handle..RubyHandle$GT$::fixnum_from_u64::_$u7b$$u7b$closure$u7d$$u7d$::h582aec1eb67449da in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.11.rcgu.o)
                magnus::r_bignum::_$LT$impl$u20$magnus..ruby_handle..RubyHandle$GT$::bignum_from_u64::hfc291dfcbe8e3d84 in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.14.rcgu.o)
            "_rb_undef_alloc_func", referenced from:
                magnus::class::Class::undef_alloc_func::h32d0625f00ac8f05 in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.6.rcgu.o)
                magnus::class::Class::undef_alloc_func::hd9b8d55a5b858df8 in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.13.rcgu.o)
            "_rb_usascii_encindex", referenced from:
                magnus::encoding::_$LT$impl$u20$magnus..ruby_handle..RubyHandle$GT$::usascii_encindex::h24a2ee09e271d12a in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.14.rcgu.o)
            "_rb_usascii_encoding", referenced from:
                magnus::encoding::_$LT$impl$u20$magnus..ruby_handle..RubyHandle$GT$::usascii_encoding::hbf307a4a51104f0e in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.14.rcgu.o)
            "_rb_utf8_encindex", referenced from:
                magnus::encoding::_$LT$impl$u20$magnus..ruby_handle..RubyHandle$GT$::utf8_encindex::he148386c0cd4c374 in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.14.rcgu.o)
            "_rb_utf8_encoding", referenced from:
                magnus::encoding::_$LT$impl$u20$magnus..ruby_handle..RubyHandle$GT$::utf8_encoding::h4497560ac4b2ca8b in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.14.rcgu.o)
            "_rb_utf8_str_new", referenced from:
                magnus::r_string::_$LT$impl$u20$magnus..ruby_handle..RubyHandle$GT$::str_new::ha487044c5e39035e in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.14.rcgu.o)
            "_rb_utf8_str_new_static", referenced from:
                magnus::r_string::_$LT$impl$u20$magnus..ruby_handle..RubyHandle$GT$::str_new_lit::h3fc8499881974362 in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.14.rcgu.o)
            "_rb_warning", referenced from:
                magnus::error::_$LT$impl$u20$magnus..ruby_handle..RubyHandle$GT$::warning::h34461cb311a3dc35 in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.14.rcgu.o)
            "_ruby_current_vm_ptr", referenced from:
                rb_sys::utils::is_ruby_vm_started::h475374df1714e929 in librb_sys-8cf493397c2d3c26.rlib(rb_sys-8cf493397c2d3c26.rb_sys.e85c0fb1-cgu.8.rcgu.o)
            "_ruby_native_thread_p", referenced from:
                magnus::ruby_handle::RubyGvlState::current::hff3e36eb5af49c2e in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.14.rcgu.o)
            "_ruby_thread_has_gvl_p", referenced from:
                magnus::ruby_handle::RubyGvlState::current::hff3e36eb5af49c2e in libmagnus-750f48931a72c806.rlib(magnus-750f48931a72c806.magnus.ed96e0ae-cgu.14.rcgu.o)
          ld: symbol(s) not found for architecture arm64
          clang: error: linker command failed with exit code 1 (use -v to see invocation)

Tried with both Ruby 3.1.4 and 3.2.2. Using chruby and ruby-install on an Apple M1 Pro. Reinstalled my rubies with --enable-shared, just to make sure and no go.

Clang bindgen on linux, windows

Attempting to use this crate with the embed feature results in the following stack trace:

error: failed to run custom build command for `rb-sys v0.9.65`

Caused by:
  process didn't exit successfully: `/home/skyrbunny/Documents/Programs/Rust/rbquest/target/debug/build/rb-sys-e899aefe966f4115/build-script-main` (exit status: 101)
  --- stdout
  cargo:rerun-if-env-changed=RUBY
  cargo:rerun-if-env-changed=RBCONFIG_ruby_version
  cargo:rerun-if-env-changed=RBCONFIG_platform
  cargo:rerun-if-env-changed=RBCONFIG_arch
  cargo:rerun-if-env-changed=RUBY_ROOT
  cargo:rerun-if-env-changed=RUBY_VERSION
  cargo:rerun-if-env-changed=RUBY
  cargo:rerun-if-changed=build/features.rs
  cargo:rerun-if-changed=build/version.rs
  cargo:rerun-if-changed=build/main.rs
  cargo:rerun-if-changed=build/ruby_macros.rs
  cargo:rerun-if-env-changed=RUBY_STATIC
  cargo:rerun-if-env-changed=RBCONFIG_ENABLE_SHARED
  cargo:rerun-if-env-changed=RBCONFIG_rubyhdrdir
  cargo:rerun-if-env-changed=RBCONFIG_rubyarchhdrdir
  cargo:rerun-if-env-changed=RBCONFIG_CPPFLAGS

  --- stderr
  Using bindgen with clang args: ["-I/usr/include/ruby-3.0.0", "-I/usr/include/x86_64-linux-gnu/ruby-3.0.0", "-fms-extensions", "-O3", "-ggdb3", "-Wall", "-Wextra", "-Wdeprecated-declarations", "-Wduplicated-cond", "-Wimplicit-function-declaration", "-Wimplicit-int", "-Wmisleading-indentation", "-Wpointer-arith", "-Wwrite-strings", "-Wimplicit-fallthrough=0", "-Wmissing-noreturn", "-Wno-cast-function-type", "-Wno-constant-logical-operand", "-Wno-long-long", "-Wno-missing-field-initializers", "-Wno-overlength-strings", "-Wno-packed-bitfield-compat", "-Wno-parentheses-equality", "-Wno-self-assign", "-Wno-tautological-compare", "-Wno-unused-parameter", "-Wno-unused-value", "-Wsuggest-attribute=format", "-Wsuggest-attribute=noreturn", "-Wunused-variable", "-Wdate-time", "-D_FORTIFY_SOURCE=2"]
  warning: unknown warning option '-Wduplicated-cond' [-Wunknown-warning-option]
  warning: unknown warning option '-Wimplicit-fallthrough=0'; did you mean '-Wimplicit-fallthrough'? [-Wunknown-warning-option]
  warning: unknown warning option '-Wno-packed-bitfield-compat' [-Wunknown-warning-option]
  warning: unknown warning option '-Wsuggest-attribute=format'; did you mean '-Wproperty-attribute-mismatch'? [-Wunknown-warning-option]
  warning: unknown warning option '-Wsuggest-attribute=noreturn' [-Wunknown-warning-option]
  /home/skyrbunny/.cargo/registry/src/github.com-1ecc6299db9ec823/rb-sys-0.9.65/wrapper.h:1:10: fatal error: 'ruby.h' file not found
  clang diag: warning: unknown warning option '-Wduplicated-cond' [-Wunknown-warning-option]
  clang diag: warning: unknown warning option '-Wimplicit-fallthrough=0'; did you mean '-Wimplicit-fallthrough'? [-Wunknown-warning-option]
  clang diag: warning: unknown warning option '-Wno-packed-bitfield-compat' [-Wunknown-warning-option]
  clang diag: warning: unknown warning option '-Wsuggest-attribute=format'; did you mean '-Wproperty-attribute-mismatch'? [-Wunknown-warning-option]
  clang diag: warning: unknown warning option '-Wsuggest-attribute=noreturn' [-Wunknown-warning-option]
  thread 'main' panicked at 'generate bindings: ClangDiagnostic("/home/skyrbunny/.cargo/registry/src/github.com-1ecc6299db9ec823/rb-sys-0.9.65/wrapper.h:1:10: fatal error: 'ruby.h' file not found\n")', /home/skyrbunny/.cargo/registry/src/github.com-1ecc6299db9ec823/rb-sys-0.9.65/build/main.rs:53:6
  stack backtrace:
     0: rust_begin_unwind
               at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/std/src/panicking.rs:584:5
     1: core::panicking::panic_fmt
               at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/panicking.rs:142:14
     2: core::result::unwrap_failed
               at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/result.rs:1814:5
     3: core::result::Result<T,E>::expect
               at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/result.rs:1064:23
     4: build_script_main::main
               at ./build/main.rs:48:25
     5: core::ops::function::FnOnce::call_once
               at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/ops/function.rs:248:5
  note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

At first, I got errors about bindgen not finding clang, but after installing as per bindgen's instructions, it started saying these things. I did a sanity heck by running the same setup process on my linux machine, with the same result.
I'm not sure how far down the chain of command this is, so I'm opening an issue here to get some pointers in the right direction.

TryConvertOwned is private; how to implement TryConvert for custom Rust structs containing collections?

Given the following Rust structs:

use magnus::{prelude::*, Error, TryConvert};

struct Topic {
  title: String,
  posts: Vec<Post>,
}

struct Post {
  body: String,
  author_id: usize,
}

impl TryConvert for Post {
    fn try_convert(val: magnus::Value) -> Result<Self, Error> {
        val.respond_to("body", false)?;
        val.respond_to("author_id", false)?;

        let body = val.funcall("body", ())?;
        let author_id = val.funcall("author_id", ())?;
        Ok(Post{body, author_id})
    }
}

impl TryConvert for Topic {
    fn try_convert(val: magnus::Value) -> Result<Self, Error> {
        val.respond_to("title", false)?;
        val.respond_to("posts", false)?;

        let title : String = val.funcall("title", ())?;
        let posts : Vec<Post> = val.funcall("posts", ())?; # <- Compiler complains here
        Ok(Title{title, posts})
    }
}

it is possible to implement TryConvert for Post as seen above.
But impossible to implement it for Topic, as Vec<Post> only implements it if Post implements TryConvertOwned, resulting in a compiler error.

However, that trait is private within Magnus (to be precise, the try_convert module which contains it is private).

Should this trait maybe be made public?
Or am I 'holding it wrong'?

Question: Is it possible to call a Rust executable with no overhead?

Hi there! I recently just finished using Magnus to call my rust function from Ruby. Behaviorally, everything works smoothly. Great job on and thank you for creating and maintaining magnus!

After commenting out 100% of my gem except for what is necessary to get all the piping in place, I noticed that it added .8 seconds of overhead. This CLI application should run in under a second in many contexts, so I'm not able to use this approach.

I am trying to explore ways to do this with less or zero overhead. For example, I was wondering if it’s possible to use magnus to make the gem executable call the rust crate’s executable directly (rather than going through magnus to serialize/deserialize between ruby and rust, and other tasks). Alternatively, if by installing the gem it could just add the rust executable to the user’s path, that’d work too. Not sure what is possible and/or best practice.

Do any folks here have any insights or recommendations?

Thank you!

Ideas

Hey @matsadler, congrats on the 0.4 release!

I just created Polars and moved Tokenizers to it and really enjoyed using it.

My Rust isn’t great, so I’m not sure what’s possible, but a few ideas:

  1. Support for &mut self / mutability in classes without having lots of borrows
  2. Support for MyClass and Vec<MyClass> as method parameters - currently needs to be &MyClass (and Vec<&MyClass> not supported)
  3. Support for iter() method on RArray

Thanks for creating this!

Panic when use ruby-static feature

I am trying to use magnus with godot-rust but got some errors that are hard to track.

The init code in the godot-rust

fn init(handle: InitHandle) {
    let _cleanup = unsafe { embed::init() };
    handle.add_class::<SomeClass>();
}

godot_init!(init); # L27

And I got this error

E 0:00:02.291   <unset>: Panic message: Ruby init code not executable
  <C++ Source>  /Users/[USER]/.cargo/registry/src/github.com-1ecc6299db9ec823/gdnative-core-0.11.3/src/private.rs:199 @ <unset>()

The lib.rs:27 is points to godot_init!(init); and the panic is raised by embed::init()
If I remove the ruby-static feature everything works correctly.

Command to build the library on macOS (Ventura 13.1)

RUBY=~/Downloads/ruby-3.2.0/dist/bin/ruby cargo build

the .cargo/config.toml is configured

P.S. if didn't use godot-rust run as a standalone binary instead of dylib, it works correctly.

Other information

nm -gU target/debug/libexample.dylib

0000000000009d20 T _godot_gdnative_init
000000000000a380 T _godot_gdnative_terminate
0000000000009d70 T _godot_nativescript_init

the nm -m target/debug/libexample.dylib can find private external symbols from ruby static library

otool -L target/debug/libexample.dylib

target/debug/libexample.dylib:
        /Users/[USER]/Desktop/Godot/target/debug/deps/libexample.dylib (compatibility version 0.0.0, current version 0.0.0)
        /System/Library/Frameworks/Security.framework/Versions/A/Security (compatibility version 1.0.0, current version 60420.60.24)
        /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 1953.255.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1319.0.0)
        /usr/local/opt/gmp/lib/libgmp.10.dylib (compatibility version 15.0.0, current version 15.1.0)
        /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
        /usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)
        /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1953.255.0)

Possibility for `Result<Vec<T>, Error>` ?

I'm running into a strange issue when trying to return a Result<Vec<TypedData>, Error> from a Rust function

   Compiling what_you_say v1.0.0 (/Users/gjtorikian/Development/yettoapp/what_you_say/ext/what_you_say)
error[E0277]: the trait bound `Result<Vec<WhatYouSayLang>, magnus::Error>: method::private::ReturnValue` is not satisfied
    --> ext/what_you_say/src/lib.rs:156:43
     |
156  |     c_lang.define_singleton_method("all", function!(WhatYouSayLang::all, 0))?;
     |                                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `method::private::ReturnValue` is not implemented for `Result<Vec<WhatYouSayLang>, magnus::Error>`
     |
     = help: the following other types implement trait `method::private::ReturnValue`:
               Result<T, magnus::Error>
               Result<Yield<I>, magnus::Error>
               Result<YieldSplat<I>, magnus::Error>
               Result<YieldValues<I>, magnus::Error>
     = note: required for `Result<Vec<WhatYouSayLang>, magnus::Error>` to implement `ReturnValue`
note: required by a bound in `Function0::<Func, Res>::new`
    --> /Users/gjtorikian/.cargo/registry/src/github.com-1ecc6299db9ec823/magnus-0.5.1/src/method.rs:3153:10
     |
3153 |     Res: ReturnValue,
     |          ^^^^^^^^^^^ required by this bound in `Function0::<Func, Res>::new`
     = note: this error originates in the macro `function` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0599]: the method `call_handle_error` exists for struct `Function0<fn() -> Result<Vec<WhatYouSayLang>, magnus::Error> {WhatYouSayLang::all}, Result<Vec<WhatYouSayLang>, magnus::Error>>`, but its trait bounds were not satisfied
   --> ext/what_you_say/src/lib.rs:156:43
    |
156 |     c_lang.define_singleton_method("all", function!(WhatYouSayLang::all, 0))?;
    |                                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method cannot be called on `Function0<fn() -> Result<Vec<WhatYouSayLang>, magnus::Error> {WhatYouSayLang::all}, Result<Vec<WhatYouSayLang>, magnus::Error>>` due to unsatisfied trait bounds
    |
   ::: /Users/gjtorikian/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/result.rs:504:1
    |
504 | pub enum Result<T, E> {
    | --------------------- doesn't satisfy `_: ReturnValue`
    |
    = note: the following trait bounds were not satisfied:
            `Result<Vec<WhatYouSayLang>, magnus::Error>: ReturnValue`
    = note: this error originates in the macro `function` (in Nightly builds, run with -Z macro-backtrace for more info)

Some errors have detailed explanations: E0277, E0599.
For more information about an error, try `rustc --explain E0277`.
error: could not compile `what_you_say` due to 2 previous errors

I am guessing it's due to this trait. Would it be possible to implement this for a vector of types as well?

Error compiling magnus v0.5.1 on arm-linux

Hello 👋
First off, thank you for this project it's great.

I've noticed a compilation error on arm-linux after updating from version 0.4 to 0.5

The error:

error[E0308]: mismatched types
   --> /home/runner/work/roaring-rb/roaring-rb/tmp/cargo-vendor/magnus/src/r_bignum.rs:295:13
    |
291 |     fn sign(self) -> u64 {
    |                      --- expected `u64` because of return type
...
295 |             r_basic.as_ref().flags & (ruby_fl_type::RUBY_FL_USER1 as VALUE)
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `u64`, found `u32`
    |
help: you can convert a `u32` to a `u64`
    |
295 |             (r_basic.as_ref().flags & (ruby_fl_type::RUBY_FL_USER1 as VALUE)).into()
    |             +                                                               ++++++++

Here is a link to the GH Action run where I've observed this: link
All other platforms successfully compile magnus so I think it's isolated to the arm-linux platform.

If someone knows off the top of their head how to fix this I'd love to meaningfully contribute by submitting a patch.

Thanks for your attention

Add Support for Passing an Array to Proc.Call

I have a scenario where I need to pass an array to a ruby proc. While using magnus proc I noticed that it treats the argument as a RArrayArgList when passing it to proc - this will mean that it gets passed as head, *rest to the ruby proc which is not idea. At the moment I just have a hash wraping it and will do a type check on the arg to the proc. Is there a way to allow arrays as arguments to procs (I tried wrapping in another array but no luck) or is there a better way to wrap the argument to the proc?

MSRV Change

I've been getting CI failures when building my magnus-dependent library on Rust 1.61. It looks like the regex crate bumped their MSRV to 1.65 (rust-lang/regex@8e13494). Since regex is a dependency of rb-sys, building magnus on Rust 1.61 seems to fail now.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.