magnus-0.7.1/.cargo/config.toml000064400000000000000000000006141046102023000144370ustar 00000000000000# 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. # # We avoid this on windows due to https://github.com/rust-lang/rust/issues/90056 [target.'cfg(not(target_family = "windows"))'] rustflags = ["-C", "link-dead-code=on"] magnus-0.7.1/.cargo_vcs_info.json0000644000000001360000000000100123330ustar { "git": { "sha1": "eb0d8ef5e2b40d718c704173bd3d52f647e24fb7" }, "path_in_vcs": "" }magnus-0.7.1/CHANGELOG.md000064400000000000000000000533131046102023000127410ustar 00000000000000# Changelog ## [Unreleased] ### Added ### Changed ### Deprecated ### Removed ### Fixed ### Security ## [0.7.1] - 2024-06-30 ### Fixed - Building docs with Ruby 3.0 ## [0.7.0] - 2024-06-30 ### Added - `Thread`, `Ruby::thread_create`/`thread_create_from_fn` and other thread APIs. - `Mutex`, `Ruby::mutex_new` and other mutex APIs. - `Fiber`, `Ruby::fiber_new`/`fiber_new_from_fn` and other fiber APIs (requires Ruby >= 3.1). - `Ruby::ary_try_from_iter` is an efficient way to create a Ruby array from a fallible Rust iterator. - `Ruby::hash_from_iter` and `Ruby::hash_try_from_iter`. - `RFile` implements `AsRawFd`. - `TypedArray`, a Ruby Array that may only contain elements of type `T`. On creation the Array is hidden from Ruby, and must be consumed to pass it to Ruby (where it reverts to a regular untyped Array). It is then inaccessible to Rust. - Implement `IntoIterator` for `RArray`. - Implement `PartialEq`, `PartialOrd`, `Add`, `Sub`, `Mul`, and `Div` for `Integer`. - `Time` with automatic conversion to/from `std::time::SystemTime`. - `Ruby::alias_variable`. - `Ruby::waitpid`. - `RHash::lookup2`. - `Ruby::define_data` new for Ruby 3.3. - `IntoError` trait for conversion to `Error`, plus `impl ReturnValue for Result where E: IntoError` to allow returning custom error types to Ruby. ### Changed - Closures/Functions used as Ruby blocks/procs take an additional first argument of `&Ruby`. ### Deprecated - `RArray::each`. Please use `ary.into_iter()` or `ary.enumeratorize("each", ())` instead. ### Removed - `deprecated-send-sync-value` feature. - `ruby-static` feature. Instead enable the feature for `rb-sys` in your Cargo.toml like so: `rb-sys = { version = "*", features = ["ruby-static"] }` - `typed_data::Obj::get`. - The `QTRUE`, `QFALSE`, and `QNIL` constants. - `Class::undef_alloc_func`. - `Value::try_convert`. - `Binding`. - `gc::{mark, mark_slice, mark_movable, location}`. - `RStruct::as_slice`. - `Exception::backtrace`. ### Fixed ### Security ## [0.6.4] - 2024-05-08 ### Fixed - Potential deadlock in `Lazy` when accessed for the first time from multiple threads simultaneously. ## [0.6.3] - 2024-03-31 ### Fixed - Missing ` IntoValueFromNative` implementation for `Vec` and `HashMap`. ## [0.6.2] - 2023-09-18 ### Fixed - Compliation error in `bytes` feature. ## [0.6.1] - 2023-08-20 ### Changed - Support `rb-sys`' `stable-api` feature. ## [0.5.5] - 2023-08-20 ### Changed - Support `rb-sys`' `stable-api` feature. - Minimum supported Rust version is 1.60. - Ruby 2.6 support requires enabling `rb-sys`' `stable-api-compiled-fallback` feature in your `Cargo.toml` like so: `rb-sys = { version = "*", default-features = false, features = ["stable-api-compiled-fallback"] }` ## [0.6.0] - 2023-07-28 ### Added - `value::Opaque` can be used to wrap a Ruby type to make it `Send` + `Sync`. - `value::Lazy` lazily initialises a Ruby value so it can be assigned to a `static`. - `value::OpaqueId` is a `Send` + `Sync` version of `value::Id`. - `value::LazyId` is an `Id` with a `const` constructor so can be assigned to a `static` and derefs to `OpaqueId`. - `error::OpaqueError` is a `Send` + `Sync` version of `Error`. - The `#[magnus(opaque_attr_reader)]` attribute can be set on `Opaque` wrapped fields of a struct when deriving `TypedData` to generate a method to return the inner value. - `Ruby::init` function to initialise Ruby when embedding Ruby in Rust that runs a function with Ruby initialised, passing `&Ruby`. - `rb_assert!()` macro to assert a Ruby expression evaluates to a truthy value. - `Class::undef_default_alloc_func` to remove a class' allocator function only if it is Ruby's default allocator function. - `Class::obj_alloc` and `Class::define_alloc_func` for allocating an object without calling `initialize`, and for defining an allocator function. - `RTypedData::wrap_as` & `typed_data::Obj::wrap_as` can be used to dynamically set the class when wrapping Rust data in a Ruby object. This can be used to allow wrapped data to be subclassed in Ruby. - `Exception::exception_class` returns the class of an exception as an `ExceptionClass`. - The `kwargs!()` macro and `KwArgs` type can be used to pass keyword arguments to `ReprValue::funcall`, et al. ### Changed - Minimum supported Rust version in now 1.61. - The `bytes-crate` feature has been renamed to `bytes`. - The `rb-sys-interop` feature has been renamed to `rb-sys`. - Ruby types are no longer `Send` or `Sync`. These types can be made `Send` & `Sync` with the `deprecated-send-sync-value` feature. This feature is meant to ease upgrading and will be removed with the next release. - `Value`'s methods moved to the `ReprValue` trait. - `typed_data::Obj` derefs to `T` so now `T`'s methods can be called directly on `typed_data::Obj`. - `ReprValue` and `TryConvert` added to `magnus::prelude`. - `typed_data::Cmp`, `typed_data::Dup` and `typed_data::Inspect` traits to help with implementing `#<=>`, `#dup` & `#clone`, and `#inspect` methods for wrapped structs. - Wrapped data must be `Sync` to enable `frozen_shareable` flag. - `TypedData::class` and `TypedData::class_for` now take a `&Ruby` argument. - `DataTypeFunctions::mark` and `DataTypeFunctions::compact` now take `&gc::Marker` and `&gc::Compactor` arguments respectively. - Init function marked with `#[magnus::init]` optionally takes a `&Ruby` argument. - Functions bound as Ruby methods with `method!()` and `function!()` optionally take `&Ruby` as a first argument. - The value returned from `embed::init` derefs to `Ruby`. - `DataTypeBuilder::new`/`DataType::builder` now take their `name` argument as a `'static CStr` (see the `magnus::data_type_builder!` macro to construct a `DataTypeBuilder` with a `'static CStr` `name`). - All methods on `DataTypeBuilder` are now `const`, so `DataType` can be constructed in a `const` context and be assigned to a `static`. - The `#[wrap]` and `#[derive(TypedData)]` macros now support setting the `#[magnus(unsafe_generics)]` attribute to allow deriving `TypedData` for types with generics. The derived implementation is not guaranteed to be correct. ### Deprecated - `typed_data::Obj::get` as it is made redundant by the `Deref` implementation for `typed_data::Obj`. - The `QTRUE`, `QFALSE`, and `QNIL` constants. Please use `value::qtrue()`, `value::qfalse()`, and `value::qnil()`. - `Class::undef_alloc_func`. Please use `Class::undef_default_alloc_func`. - `Value::try_convert`, prefer `TryConvert::try_convert` or `T::try_convert`. - `Binding`. To call methods on Ruby's binding, use `ReprValue::funcall` on a `Value` know to be an instance of Ruby's Binding class. - `gc::{mark, mark_slice, mark_movable, location}`, instead use `gc::Marker::{mark, mark_slice, mark_movable}` and `Compactor::location`. - `RStruct::as_slice`. ### Removed - Inherent methods on `RClass`, import `magnus::Class` trait or `magnus::prelude::*`. - `Into`/`From for Value` implementations, use `IntoValue`. - `Default` implementations for `Value`, `RClass`, and `ExceptionClass`. - `Error` can no longer be directly constructed, must use `Error::new` or `Error::from`/`Exception::into`. - `RString::append` (use `RString::buf_append`). - `Error::runtime_error` (use `Error::new(exception::runtime_error(), msg)`). ### Fixed - `RFloat::from_value` now returns `None` when value is a `Flonum`. ## [0.5.3] - 2023-04-07 ### Added - `Value::as_value` method that can be called on any Ruby type (as all Ruby types deref to `Value`) as a forward-compatible way to convert a Ruby type to `Value`. ## [0.5.2] - 2023-03-19 ### Fixed - Fixed compilation issue in `RBignum` on 32 bit platforms. ## [0.5.1] - 2023-02-18 ### Fixed - Documentation fixes. ## [0.5.0] - 2023-02-11 ### Added - `typed_data::Obj`, a Ruby object wrapping a Rust type known to be `T`. - `RArray::to_ary`, `RArray::assoc`, `RArray::rassoc`, and `RArray::cmp`. - `RHash::with_capacity` new for Ruby 3.2. - `RHash::bulk_insert`. - `Value::hash` to generate a hash key for a Ruby object. - `typed_data::Hash` and `typed_data::IsEql` traits to help with implementing `#hash` and `#eql?` methods for wrapped structs. - Lots of `RString` methods: `capacity`, `cmp`, `comparable`, `drop_bytes`, `dump`, `ellipsize`, `offset`, `plus`, `times`, `replace`, `scrub`, `shared_replace`, `split`, `update`. - `RRegexp::new`/`new_str`, `RRegexp::reg_match`, and `RRegexp::options`. - The `backref_get` function to get the `RMatch` for the last regexp match. - `RMatch::nth_defined`, `RMatch::nth_match`, `RMatch::backref_number`, `RMatch::matched`, `RMatch::pre`, `RMatch::post`, and `RMatch::last` - `RBignum::is_positive`/`is_negative`. - `new`, `num`, and `den` for `RRational`, plus `rationalize` and `rationalize_with_prec` for `Float`. - `RComplex::new`, `polar`, `real`, `imag`, `conjugate`, `abs` & `arg`. - New `Numeric` trait implemented for `Integer`, `Float`, `RRational`, `RComplex`, etc. Exposes functions for applying operators to numeric types following Ruby's coercion protocol. - `value::Id::new` added to replace `value::Id::from`. - `TypedData::class_for` can be implemented to customise the class on a case by case basis when wrapping Rust data in a Ruby object. - The `#[wrap]` and `#[derive(TypedData)]` macros now allow setting `#[magnus(class = "...")]` on enum variants to customise the class per variant when wrapping enums in a Ruby object. - `Value::funcall_public` calls a public method, returning an error for a private or protected method. - `error::Result` is shorthand for `std::result::Result`. - `embed::setup` to perform minimal initialisation of Ruby for environments where `embed::init` doesn't work. - The `bytes-crate` feature can be enabled to allow automatic conversions between `bytes::Bytes` and Ruby strings. ### Changed - When converting Ruby values to `RArray` (or `Vec`, `[T; 1]`, or `(T,)`), an error will be returned if given a non-`Array` (or non-`to_ary`-able) value, rather than wrapping it in an array (see `RArray::to_ary` for the old behaviour). - `r_typed_data::{DataType, DataTypeFunctions, DataTypeBuilder, TypedData}` all moved to `typed_data` module, `r_typed_data` module removed. This should only affect `DataTypeBuilder` as it was the only one not exported at the crate root. - `gc::adjust_memory_usage`'s argument changed to `isize`, rather than `i32` or `i64` depending on pointer width. - The `IntoValue` trait is now used instead of `Into` for converting to Ruby types. - `IntoId`, `IntoRString`, and `IntoSymbol` are used instead of `Into`, `Into`, and `Into`. - `use magnus::prelude::*` will now import traits anonymously. - `EncodingCapable`, `Module`, `Class`, and `Object` traits can no longer be implemented by user code. - The `wb_protected` flag is automatically set for data wrapped in a Ruby object when `mark` is not set. ### Deprecated - `RString::append` (use `RString::buf_append`). - `Error::runtime_error` (use `Error::new(exception::runtime_error(), msg)`). - Implementations of `Into` (use `IntoValue`). ### Removed - `DataTypeBuilder::free_immediatly` (use `free_immediately`). - `From<&str> for RString` / `Into for &str`. - `From for RString` / `Into for String`. - `From<&str> for Symbol` / `Into for &str`. - `From for Symbol` / `Into for String`. - `From<&str> for StaticSymbol` / `Into for &str`. - `From for StaticSymbol` / `Into for String`. - `From<&str> for Id` / `Into for &str`. - `From for Id` / `Into for String`. - Internal `debug_assert_value!()` macro no longer public. ### Fixed - Missing `ReprValue` implementation for `RStruct`. ## [0.4.4] - 2022-12-24 ### Added - `Class::undef_alloc_func`, a function to remove a class' allocator function. ### Fixed - 'wrapped' structs from `#[wrap]` and `#[derive(TypedData)]` macros will not generate `warning: undefining the allocator of T_DATA class` under Ruby 3.2 ## [0.4.3] - 2022-12-07 ### Fixed - `gc::mark_slice` was skipping the last element of the slice. ## [0.4.2] - 2022-11-30 ### Fixed - Removed errant `dbg!()`. ## [0.4.1] - 2022-11-29 ### Fixed - `scan_args::get_kwargs` error/segfault when leading optional args were not provided, due to trying to convert the type of the missing value. ## [0.4.0] - 2022-11-19 ### Added - `Value::funcall_with_block`. - impl `TryConvert` for `Exception` and `ExceptionClass`. - `Class` trait (implemented for `RClass` and `ExceptionClass`). - `define_error` and `Module::define_error` helpers for defining an Exception Class. - `RTypedData::wrap` and `RTypedData::get` inherent methods for wrapping Rust types in Ruby objects. - Support for Ruby 3.2 preview. - Support for mswin platform (msvc) on Windows with Ruby 3.2 (in addition to the mingw support previously available for all Ruby versions on Windows). ### Changed - Switched to rb-sys for low level bindings. - Rust types wrapped in Ruby objects must be `Send`. - Only function pointers (fn or non-capturing closure) are accepted as argument for `Value::block_call`. Use `Proc::from_fn` + `Value::funcall_with_block` for closures that capture variables. - `Proc::new` only accepts a function pointer, use `Proc::from_fn` for closures that capture variables. - `ExceptionClass::default()` now returns `StandardError` rather than `RuntimeError`. - `TryConvert` now takes `Value` by value (rather than a reference). ### Deprecated - `DataTypeBuilder::free_immediatly` (use `free_immediately`). - `free_immediatly` attribute in `wrap` macro (use `free_immediately`). - `free_immediatly` in `magnus` attribute of `derive(TypedData)` macro (use `free_immediately`). ### Removed - `String::encode_utf8`, use `r_string.conv_enc(RbEncoding::utf8())` instead. - `Value::leak`, use `gc::register_mark_object` instead. - `define_global_variable` (use `define_variable`). ### Fixed - Memory leak of the message when returning an `Error` to raise an exception. - `Flonum` support disabled for Ruby built with USE_FLONUM=0 (e.g. 32 bit systems). - Correct spelling of `free_immediatly` (to `free_immediately`) in the `DataTypeBuilder` struct, and `wrap` and `derive(TypedData)` macros. ### Security - `printf`-style format strings no longer interpreted in error messages when automatically raised as Ruby exceptions. ## [0.3.2] - 2022-05-29 ### Fixed - Better error output from build script when `ruby` can't be found or errors. - Fixed crash in `Proc::new` and `Value::block_call` when the proc was stored and called later. ## [0.3.1] - 2022-05-21 ### Fixed - Integer overflow in Ruby Float to f64 conversion ## [0.3.0] - 2022-05-18 ### Added - `RString::new_shared` and `RString::new_frozen`. - `encoding` module, including `encoding::Index` and `RbEncoding` types. - `RString::enc_coderange` and related methods. - `RString::codepoints` and `RString::char_bytes` iterators over string contents. - The following methods for `RArray`: `dup`, `concat`, `plus`, `delete`, `delete_at`, `resize`, `reverse`, `rotate`, and `sort`. - New methods for `RString`: `enc_new`, `len`, `length`, and `is_empty`. - `RHash` gains the methods `delete` and `clear`. - `require`, `current_receiver`, `call_super`, and `define_global_const` functions. - `Object::singleton_class` and `Object::extend_object`. - `Proc::new`, `Proc::arity`, and `Proc::is_lambda`. - `superclass` and `name` methods for `RClass`. - `scan_args::check_arity`. - Methods for `Module`: `include_module`, `prepend_module`, `const_set`, `ancestors`, `define_attr`, and `define_alias`. - `rb-sys-interop` feature to use low level bindings from rb-sys, and `rb_sys` module to expose functions for working with rb-sys. - Added `gc::register_mark_object`, `gc::register_address`, `gc::unregister_address`, `gc::count`, `gc::stat`, and `gc::all_stats`. - `Error::iter_break`. - `StaticSymbol::check` and `Id::check` to check if a symbol exists. ### Changed - `RArray::cat`, `RArray::from_slice`, and `gc::mark_slice` will accept a slice of any Ruby type as an argument, rather than only a slice of `Value`. This may change type inference rules such that things like `RArray::from_slice(&[1.into()])` will no longer work. Use `RArray::from_slice(&[Value::from(1)])` instead. - Similar to above, `gc::location` will accept any Ruby type as an argument. - `BoxValue` can hold any Ruby type, not just `Value`. - Improved performance for conversion between Ruby floats/integers and Rust types. - The parameters to the closure passed to `RHash::foreach` will now be automatically converted from `Value` to Rust types. - `Module::define_method`, `Module::define_private_method`, `Module::define_protected_method`, `RModule::define_singleton_method`, and `Object::define_singleton_method` all return `Result<(), Error>` rather than `()` as they may fail in the unusual case that the receiver is frozen. ### Deprecated - `String::encode_utf8`, use `r_string.conv_enc(RbEncoding::utf8())` instead. - `Value::leak`, use `gc::register_mark_object` instead. - `define_global_variable` (use `define_variable`) to better match Ruby C API naming. - `Exception::backtrace`, use `ex.funcall("backtrace", ())` instead. ### Removed - `error::protect` removed as it should not be needed when using Magnus. For use with rb-sys enable the `rb-sys-interop` feature and use `magnus::rb_sys::protect`. - `Qfalse::new`, `Qnil::new`, `Qtrue::new`, `Qundef::new` (use QFALSE/QNIL/QTRUE/QUNDEF). - Functions for generating an `Error` with a specific Ruby type. E.g. `Error::type_error("...")`, use `Error::new(exception::type_error(), "...")`. ### Fixed - creating a `StaticSymbol` from a `&str` with characters outside the ASCII range. - panicking in any of the functions of `DataTypeFunctions` will abort the process to avoid undefined behaviour. - panicking in the closure passed to `RHash::foreach` won't result in undefined behaviour. ## [0.2.1] - 2022-04-03 ### Fixed - Fixed compilation error in `method!` and `function!` macros with arity argument of 5. ## [0.2.0] - 2022-03-31 ### Added - Functions in `class`, `module`, and `error` modules to access built-in classes/modules. - Many doc examples. - `RArray::len`, `RArray::includes`, `RArray::join`, `RArray::is_shared`, `RArray::replace`, and `RArray::subseq`. - Implement `From<&str>` and `From` for `RString`. - Support for `Range`. - Pre-built bindings for Ruby 3.1 on Windows. - Support calling Ruby methods with Rust closure as a Ruby block. - `Class::new` and `Module::new` for creating anonymous classes/modules. - `r_string!` macro to create Ruby string from a `str` literal. - `Value::equal` and `Value::eql` for object equality. - `Value::respond_to` and `Value::check_funcall` for conditionally calling Ruby methods only when they are defined. - `scan_args` and `get_kwargs` for complex argument parsing. ### Changed - `Qundef::to_value` now marked `unsafe`. - `RArray::cat`, `RArray::push`, `RArray::unshift`, and `RArray::store` now return `Result<(), Error>`. - `eval!` macro uses anonymous (rather than caller's) binding. ### Deprecated - `Qfalse::new`, `Qnil::new`, `Qtrue::new`, `Qundef::new` (use QFALSE/QNIL/QTRUE/QUNDEF). - Functions for generating an `Error` with a specific Ruby type. E.g. `Error::type_error("...")` is now `Error::new(exception::type_error(), "...")` - `Binding::new`. This will be removed in the future as the underlying `rb_binding_new` will not function as of Ruby 3.2. ### Fixed - Converting Ruby integers to `isize`/`i64`/`usize`/`u64` on Windows. - Edge case where static symbol created after a dynamic symbol with the same name wouldn't be detected as static. - Many `RArray` methods now correctly protect from exceptions (instead returning `Result<_, Error>` when an exception occurs). ## [0.1.0] - 2022-02-25 ### Added - Support for most core classes, `String`, `Symbol`, `Integer`, `Float`, `Array`, `Hash` and more. - Defining Rust methods as Ruby functions. - Calling Ruby methods from Rust. - Automatic type conversion between Rust and Ruby types. - Conversion from Ruby exceptions to Rust Results and visa versa. - Support for wrapping custom Rust structs as Ruby objects. - `Enumerator` as a iterator. - `yield` to Ruby blocks. - `#[init]` macro to mark init function to load extension with `require`. - Pre-built bindings for Ruby 2.6 - 3.1 on common platforms, build-time generated bindings otherwise. [Unreleased]: https://github.com/matsadler/magnus/compare/0.7.1...HEAD [0.7.1]: https://github.com/matsadler/magnus/compare/0.7.0...0.7.1 [0.7.0]: https://github.com/matsadler/magnus/compare/0.6.4...0.7.0 [0.6.4]: https://github.com/matsadler/magnus/compare/0.6.3...0.6.4 [0.6.3]: https://github.com/matsadler/magnus/compare/0.6.2...0.6.3 [0.6.2]: https://github.com/matsadler/magnus/compare/0.6.1...0.6.2 [0.6.1]: https://github.com/matsadler/magnus/compare/0.6.0...0.6.1 [0.5.5]: https://github.com/matsadler/magnus/compare/0.5.4...0.5.5 [0.6.0]: https://github.com/matsadler/magnus/compare/0.5.3...0.6.0 [0.5.3]: https://github.com/matsadler/magnus/compare/0.5.2...0.5.3 [0.5.2]: https://github.com/matsadler/magnus/compare/0.5.1...0.5.2 [0.5.1]: https://github.com/matsadler/magnus/compare/0.5.0...0.5.1 [0.5.0]: https://github.com/matsadler/magnus/compare/0.4.4...0.5.0 [0.4.4]: https://github.com/matsadler/magnus/compare/0.4.3...0.4.4 [0.4.3]: https://github.com/matsadler/magnus/compare/0.4.2...0.4.3 [0.4.2]: https://github.com/matsadler/magnus/compare/0.4.1...0.4.2 [0.4.1]: https://github.com/matsadler/magnus/compare/0.4.0...0.4.1 [0.4.0]: https://github.com/matsadler/magnus/compare/0.3.2...0.4.0 [0.3.2]: https://github.com/matsadler/magnus/compare/0.3.1...0.3.2 [0.3.1]: https://github.com/matsadler/magnus/compare/0.3.0...0.3.1 [0.3.0]: https://github.com/matsadler/magnus/compare/0.2.1...0.3.0 [0.2.1]: https://github.com/matsadler/magnus/compare/0.2.0...0.2.1 [0.2.0]: https://github.com/matsadler/magnus/compare/0.1.0...0.2.0 [0.1.0]: https://github.com/matsadler/magnus/tree/0.1.0 magnus-0.7.1/Cargo.lock0000644000000171320000000000100103120ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" dependencies = [ "memchr", ] [[package]] name = "bindgen" version = "0.69.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ffcebc3849946a7170a05992aac39da343a90676ab392c51a4280981d6379c2" dependencies = [ "bitflags", "cexpr", "clang-sys", "lazy_static", "lazycell", "peeking_take_while", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", "syn", ] [[package]] name = "bitflags" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "bytes" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cexpr" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ "nom", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clang-sys" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" dependencies = [ "glob", "libc", "libloading", ] [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lazycell" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libloading" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", "winapi", ] [[package]] name = "magnus" version = "0.7.1" dependencies = [ "bytes", "magnus-macros", "rb-sys", "rb-sys-env", "seq-macro", ] [[package]] name = "magnus-macros" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5968c820e2960565f647819f5928a42d6e874551cab9d88d75e3e0660d7f71e3" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "peeking_take_while" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "proc-macro2" version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] [[package]] name = "rb-sys" version = "0.9.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05b780e6858b0b0eced1d55d0f097c024b77a37b41f83bd35341130f78e37c51" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" version = "0.9.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44957a3bc513dad1b0f20bdd0ee3b82e729a59da44086a6b40d8bc71958a6db8" dependencies = [ "bindgen", "lazy_static", "proc-macro2", "quote", "regex", "shell-words", "syn", ] [[package]] name = "rb-sys-env" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a35802679f07360454b418a5d1735c89716bde01d35b1560fc953c1415a0b3bb" [[package]] name = "regex" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "seq-macro" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "shell-words" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "shlex" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "syn" version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" magnus-0.7.1/Cargo.toml0000644000000036270000000000100103410ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "magnus" version = "0.7.1" exclude = [ ".github", ".gitignore", ] description = "High level Ruby bindings. Write Ruby extension gems in Rust, or call Ruby code from a Rust binary." homepage = "https://github.com/matsadler/magnus" documentation = "https://docs.rs/magnus/" readme = "README.md" keywords = [ "ruby", "rubygem", "extension", "gem", ] categories = [ "api-bindings", "development-tools::ffi", ] license = "MIT" repository = "https://github.com/matsadler/magnus" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [lib] doc-scrape-examples = false [[example]] name = "fibonacci" doc-scrape-examples = false [[example]] name = "hello_world" doc-scrape-examples = false [[example]] name = "mut_point" doc-scrape-examples = false [[example]] name = "point" doc-scrape-examples = false [dependencies.bytes] version = "1" optional = true [dependencies.magnus-macros] version = "0.6.0" [dependencies.rb-sys] version = "0.9.85" features = [ "bindgen-rbimpls", "bindgen-deprecated-types", "stable-api", ] default-features = false [dependencies.seq-macro] version = "0.3" [dev-dependencies.rb-sys] version = "0.9" features = ["stable-api-compiled-fallback"] default-features = false [build-dependencies.rb-sys-env] version = "0.1.2" [features] bytes = ["dep:bytes"] default = ["old-api"] embed = ["rb-sys/link-ruby"] old-api = [] rb-sys = [] magnus-0.7.1/Cargo.toml.orig000064400000000000000000000033261046102023000140160ustar 00000000000000[package] name = "magnus" version = "0.7.1" edition = "2021" description = "High level Ruby bindings. Write Ruby extension gems in Rust, or call Ruby code from a Rust binary." keywords = ["ruby", "rubygem", "extension", "gem"] categories = ["api-bindings", "development-tools::ffi"] repository = "https://github.com/matsadler/magnus" homepage = "https://github.com/matsadler/magnus" documentation = "https://docs.rs/magnus/" license = "MIT" exclude = [".github", ".gitignore"] [workspace] members = ["magnus-macros"] exclude = [ "examples/rust_blank/ext/rust_blank", "examples/custom_exception_ruby/ext/ahriman", "examples/custom_exception_rust/ext/ahriman", "examples/complete_object/ext/temperature", ] [features] default = ["old-api"] bytes = ["dep:bytes"] embed = ["rb-sys/link-ruby"] old-api = [] rb-sys = [] [dependencies] bytes = { version = "1", optional = true } magnus-macros = { version = "0.6.0", path = "magnus-macros" } rb-sys = { version = "0.9.85", default-features = false, features = [ "bindgen-rbimpls", "bindgen-deprecated-types", "stable-api", ] } seq-macro = "0.3" [dev-dependencies] magnus = { path = ".", default-features = false, features = [ "embed", "rb-sys", "bytes", ] } rb-sys = { version = "0.9", default-features = false, features = [ "stable-api-compiled-fallback", ] } [build-dependencies] rb-sys-env = "0.1.2" [lib] doc-scrape-examples = false [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [[example]] name = "fibonacci" doc-scrape-examples = false [[example]] name = "hello_world" doc-scrape-examples = false [[example]] name = "mut_point" doc-scrape-examples = false [[example]] name = "point" doc-scrape-examples = false magnus-0.7.1/Gemfile000064400000000000000000000001611046102023000124140ustar 00000000000000source "https://rubygems.org" gem "rb_sys", "~> 0.9.56" gem "rake" gem "rake-compiler", "1.2.0" gem "test-unit" magnus-0.7.1/LICENSE000064400000000000000000000020731046102023000121320ustar 00000000000000MIT License Copyright (c) 2023, 2022, 2021 Matthew Sadler Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. magnus-0.7.1/README.md000064400000000000000000000437521046102023000124150ustar 00000000000000# 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] [API Docs]: https://docs.rs/magnus/latest/magnus/ [GitHub]: https://github.com/matsadler/magnus [crates.io]: https://crates.io/crates/magnus [Getting Started] | [Type Conversions] | [Safety] | [Compatibility] [Getting Started]: #getting-started [Type Conversions]: #type-conversions [Safety]: #safety [Compatibility]: #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): ```rust 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): ```rust 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. ```rust 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. ```rust 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: ```rust struct Point { x: isize, y: isize, } #[magnus::wrap(class = "Point")] struct MutPoint(std::cell::RefCell); 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: ``` rust #[derive(Default)] struct Point { x: isize, y: isize, } #[derive(Default)] #[wrap(class = "Point")] struct MutPoint(RefCell); 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::(); 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`** ```toml [lib] crate-type = ["cdylib"] [dependencies] magnus = "0.7" ``` 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`** ```rust 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`** ```ruby 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`** ```ruby 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`** ```ruby 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. [`rb_sys` gem]: https://github.com/oxidize-rb/rb-sys/tree/main/gem [`rake-compiler`]: https://github.com/rake-compiler/rake-compiler [`rust_blank`]: https://github.com/matsadler/magnus/tree/main/examples/rust_blank/ext/rust_blank [`rb-sys` example project]: https://github.com/oxidize-rb/rb-sys/tree/main/examples/rust_reverse ### Embedding Ruby in Rust To call Ruby from a Rust program, enable the `embed` feature: **`Cargo.toml`** ```toml [dependencies] magnus = { version = "0.7", 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`** ```rust 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 `Result`s and vice versa. These conversions follow the pattern set by Ruby's core and standard libraries, where many conversions will delegate to a `#to_` method if the object is not of the requested type, but does implement the `#to_` 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` 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]`, `#to_ary` | | `HashMap`\* | `{K => V}`, `#to_hash` | | `&T`, `typed_data::Obj` where `T: TypedData`\*\* | instance of `::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` or `nil` | | `Result` (return only) | `T` or raises error | | `(T, U)`, `(T, U, V)`, etc, `[T; N]`, `Vec` | `Array` | | `HashMap` | `Hash` | | `std::time::SystemTime` | `Time` | | `T`, `typed_data::Obj` where `T: TypedData`\*\* | instance of `::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. [Serde]: https://github.com/serde-rs/serde [`serde_magnus`]: https://github.com/OneSignal/serde-magnus ### 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: ```rust 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`: ```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][plat]. Magnus is not tested on 32 bit systems. Efforts are made to ensure it compiles. Patches are welcome. [plat]: https://github.com/oxidize-rb/rb-sys#supported-platforms ## 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`][rb_sys_module] module for advanced interoperability with rb-sys, allows you to access low-level Ruby APIs which Magnus does not expose. [rb-sys]: https://github.com/oxidize-rb/rb-sys/tree/main/crates/rb-sys [rb_sys_module]: https://docs.rs/magnus/latest/magnus/rb_sys/index.html ### `serde_magnus` [`serde_magnus`] integrates [Serde] and Magnus for seamless serialisation and deserialisation of Rust to Ruby data structures and vice versa. ## Users * [`halton`](https://github.com/matsadler/halton-rb) a Ruby gem providing a highly optimised method for generating Halton sequences. Please open a [pull request](https://github.com/matsadler/magnus/pulls) 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: ```toml [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-0.7.1/build.rs000064400000000000000000000001511046102023000125650ustar 00000000000000fn main() -> Result<(), Box> { let _ = rb_sys_env::activate()?; Ok(()) } magnus-0.7.1/examples/complete_object/Rakefile000064400000000000000000000005671046102023000175540ustar 00000000000000# frozen_string_literal: true require "rake/testtask" require "rake/extensiontask" task default: :test Rake::ExtensionTask.new("temperature") do |c| c.lib_dir = "lib/temperature" end task :dev do ENV['RB_SYS_CARGO_PROFILE'] = 'dev' end Rake::TestTask.new do |t| t.deps << :dev << :compile t.test_files = FileList[File.expand_path("test/*_test.rb", __dir__)] end magnus-0.7.1/examples/complete_object/lib/temperature.rb000064400000000000000000000001121046102023000215210ustar 00000000000000# frozen_string_literal: true require_relative "temperature/temperature" magnus-0.7.1/examples/complete_object/test/temperature_test.rb000064400000000000000000000067011046102023000230030ustar 00000000000000# frozen_string_literal: true require "test/unit" require_relative "../lib/temperature" class TemperatureTest < Test::Unit::TestCase def test_new assert { Temperature.new(kelvin: 292.65).to_kelvin == 292.65 } assert { Temperature.new(celsius: 19.5).to_celsius == 19.5 } assert { Temperature.new(fahrenheit: 67.1).to_fahrenheit == 67.1 } end def test_to_kelvin assert { Temperature.new(kelvin: 292.65).to_kelvin == 292.65 } assert { Temperature.new(celsius: 19.5).to_kelvin == 292.65 } assert { Temperature.new(fahrenheit: 67.1).to_kelvin == 292.65 } end def test_to_celsius assert { Temperature.new(kelvin: 292.65).to_celsius == 19.5 } assert { Temperature.new(celsius: 19.5).to_celsius == 19.5 } assert { Temperature.new(fahrenheit: 67.1).to_celsius == 19.5 } end def test_to_fahrenheit assert { Temperature.new(kelvin: 292.65).to_fahrenheit == 67.1 } assert { Temperature.new(celsius: 19.5).to_fahrenheit == 67.1 } assert { Temperature.new(fahrenheit: 67.1).to_fahrenheit == 67.1 } end def test_dup temp = Temperature.new(celsius: 19.5) def temp.singlton_method_example end copy = temp.dup assert { temp.object_id != copy.object_id } assert { temp == copy } assert { !copy.frozen? } assert { !copy.respond_to?(:singlton_method_example)} temp2 = Temperature.new(celsius: 19.5) temp2.freeze copy2 = temp2.dup assert { !copy2.frozen? } end def test_clone temp = Temperature.new(celsius: 19.5) def temp.singlton_method_example end copy = temp.clone assert { temp.object_id != copy.object_id } assert { temp == copy } assert { !copy.frozen? } assert { copy.respond_to?(:singlton_method_example)} temp2 = Temperature.new(celsius: 19.5) temp2.freeze copy2 = temp2.clone assert { copy2.frozen? } temp3 = Temperature.new(celsius: 19.5) copy3 = temp3.clone(freeze: true) assert { copy3.frozen? } temp4 = Temperature.new(celsius: 19.5) temp4.freeze copy4 = temp4.clone(freeze: false) assert { !copy4.frozen? } end def test_hash freezing = Temperature.new(celsius: 0) boiling = Temperature.new(celsius: 100) hash = {freezing => :cold, boiling => :hot} assert { hash[Temperature.new(fahrenheit: 32)] == :cold } assert { hash[Temperature.new(fahrenheit: 212)] == :hot } end def test_sort temps = [Temperature.new(celsius: 25), Temperature::new(fahrenheit: 80), Temperature::new(kelvin: 273.15)] assert { temps.sort == [Temperature::new(kelvin: 273.15), Temperature.new(celsius: 25), Temperature::new(fahrenheit: 80)] } assert { Temperature.new(celsius: 40) > Temperature.new(fahrenheit: 90)} assert { Temperature.new(fahrenheit: 32) < Temperature.new(celsius: 1)} assert { Temperature.new(celsius: -40) == Temperature.new(fahrenheit: -40 )} end def test_inspect assert { Temperature.new(celsius: 19.5).inspect == "Temperature { microkelvin: RefCell { value: 292650000 } }" } end def test_to_s assert { Temperature.new(celsius: 19.5).to_s == "19.5°C" } end class OffsetTemperature < Temperature def initialize(offset, **kwargs) kwargs[:kelvin] += offset if kwargs.key?(:kelvin) kwargs[:celsius] += offset if kwargs.key?(:celsius) kwargs[:fahrenheit] += offset if kwargs.key?(:fahrenheit) super(**kwargs) end end def test_subclass assert { OffsetTemperature.new(2,celsius: 19.5).to_s == "21.5°C" } end end magnus-0.7.1/examples/custom_exception_ruby/Rakefile000064400000000000000000000005571046102023000210460ustar 00000000000000# frozen_string_literal: true require "rake/testtask" require "rake/extensiontask" task default: :test Rake::ExtensionTask.new("ahriman") do |c| c.lib_dir = "lib/ahriman" end task :dev do ENV['RB_SYS_CARGO_PROFILE'] = 'dev' end Rake::TestTask.new do |t| t.deps << :dev << :compile t.test_files = FileList[File.expand_path("test/*_test.rb", __dir__)] end magnus-0.7.1/examples/custom_exception_ruby/lib/ahriman/error.rb000064400000000000000000000001661046102023000232400ustar 00000000000000# frozen_string_literal: true module Ahriman class Error < StandardError; end class RubricError < Error; end end magnus-0.7.1/examples/custom_exception_ruby/lib/ahriman.rb000064400000000000000000000001431046102023000221020ustar 00000000000000# frozen_string_literal: true require_relative "ahriman/error" require_relative "ahriman/ahriman" magnus-0.7.1/examples/custom_exception_ruby/test/error_test.rb000064400000000000000000000003331046102023000230650ustar 00000000000000# frozen_string_literal: true require "test/unit" require_relative "../lib/ahriman" class ErrorTest < Test::Unit::TestCase def test_error? assert_raise(Ahriman::RubricError) { Ahriman::cast_rubric } end end magnus-0.7.1/examples/custom_exception_rust/Rakefile000064400000000000000000000005571046102023000210620ustar 00000000000000# frozen_string_literal: true require "rake/testtask" require "rake/extensiontask" task default: :test Rake::ExtensionTask.new("ahriman") do |c| c.lib_dir = "lib/ahriman" end task :dev do ENV['RB_SYS_CARGO_PROFILE'] = 'dev' end Rake::TestTask.new do |t| t.deps << :dev << :compile t.test_files = FileList[File.expand_path("test/*_test.rb", __dir__)] end magnus-0.7.1/examples/custom_exception_rust/lib/ahriman.rb000064400000000000000000000001021046102023000221110ustar 00000000000000# frozen_string_literal: true require_relative "ahriman/ahriman" magnus-0.7.1/examples/custom_exception_rust/test/error_test.rb000064400000000000000000000003331046102023000231010ustar 00000000000000# frozen_string_literal: true require "test/unit" require_relative "../lib/ahriman" class ErrorTest < Test::Unit::TestCase def test_error? assert_raise(Ahriman::RubricError) { Ahriman::cast_rubric } end end magnus-0.7.1/examples/fibonacci.rs000064400000000000000000000005601046102023000152250ustar 00000000000000fn fib(n: usize) -> usize { match n { 0 => 0, 1 | 2 => 1, _ => fib(n - 1) + fib(n - 2), } } fn main() { magnus::Ruby::init(|ruby| { ruby.define_global_function("fib", magnus::function!(fib, 1)); ruby.eval::("p (0..12).map {|n| fib(n)}") .unwrap(); Ok(()) }) .unwrap() } magnus-0.7.1/examples/hello_world.rs000064400000000000000000000005041046102023000156200ustar 00000000000000fn hello(subject: String) -> String { format!("hello, {}", subject) } fn main() { magnus::Ruby::init(|ruby| { ruby.define_global_function("hello", magnus::function!(hello, 1)); ruby.eval::(r#"puts hello("world")"#) .unwrap(); Ok(()) }) .unwrap() } magnus-0.7.1/examples/mut_point.rs000064400000000000000000000027121046102023000153270ustar 00000000000000use std::cell::RefCell; use magnus::{function, method, prelude::*, wrap}; struct Point { x: isize, y: isize, } #[wrap(class = "Point")] struct MutPoint(RefCell); impl MutPoint { fn new(x: isize, y: isize) -> Self { Self(RefCell::new(Point { x, y })) } fn x(&self) -> isize { self.0.borrow().x } fn set_x(&self, val: isize) { self.0.borrow_mut().x = val; } fn y(&self) -> isize { self.0.borrow().y } fn set_y(&self, val: isize) { self.0.borrow_mut().y = val; } fn distance(&self, other: &MutPoint) -> f64 { (((other.x() - self.x()).pow(2) + (other.y() - self.y()).pow(2)) as f64).sqrt() } } fn main() -> Result<(), String> { magnus::Ruby::init(|ruby| { let class = ruby.define_class("Point", ruby.class_object())?; class.define_singleton_method("new", function!(MutPoint::new, 2))?; class.define_method("x", method!(MutPoint::x, 0))?; class.define_method("x=", method!(MutPoint::set_x, 1))?; class.define_method("y", method!(MutPoint::y, 0))?; class.define_method("y=", method!(MutPoint::set_y, 1))?; class.define_method("distance", method!(MutPoint::distance, 1))?; let d: f64 = ruby.eval( "a = Point.new(0, 0) b = Point.new(0, 0) b.x = 5 b.y = 10 a.distance(b)", )?; println!("{}", d); Ok(()) }) } magnus-0.7.1/examples/point.rs000064400000000000000000000020021046102023000144320ustar 00000000000000use magnus::{function, method, prelude::*, wrap}; #[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() } } fn main() -> Result<(), String> { magnus::Ruby::init(|ruby| { 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))?; let d: f64 = ruby.eval( "a = Point.new(0, 0) b = Point.new(5, 10) a.distance(b)", )?; println!("{}", d); Ok(()) }) } magnus-0.7.1/examples/rust_blank/Rakefile000064400000000000000000000005651046102023000165600ustar 00000000000000# frozen_string_literal: true require "rake/testtask" require "rake/extensiontask" task default: :test Rake::ExtensionTask.new("rust_blank") do |c| c.lib_dir = "lib/rust_blank" end task :dev do ENV['RB_SYS_CARGO_PROFILE'] = 'dev' end Rake::TestTask.new do |t| t.deps << :dev << :compile t.test_files = FileList[File.expand_path("test/*_test.rb", __dir__)] end magnus-0.7.1/examples/rust_blank/lib/rust_blank.rb000064400000000000000000000001101046102023000203340ustar 00000000000000# frozen_string_literal: true require_relative "rust_blank/rust_blank" magnus-0.7.1/examples/rust_blank/test/bench.rb000064400000000000000000000012431046102023000174700ustar 00000000000000require "benchmark" require_relative "../lib/rust_blank" n = 1_000_000 Benchmark.bmbm do |x| x.report("empty") do n.times {"".blank?} end x.report("blank") do n.times {" ".blank?} end x.report("present") do n.times {"x".blank?} end x.report("lots of spaces blank") do n.times {" ".blank?} end x.report("lots of spaces present") do n.times {" x".blank?} end x.report("blank US-ASCII") do s = " ".encode("US-ASCII") n.times {s.blank?} end x.report("blank non-utf-8") do s = " ".encode("UTF-16LE") n.times {s.blank?} end end magnus-0.7.1/examples/rust_blank/test/blank_test.rb000064400000000000000000000007141046102023000205410ustar 00000000000000# frozen_string_literal: true require "test/unit" require_relative "../lib/rust_blank" class BlankTest < Test::Unit::TestCase def test_blank? assert { "".blank? } assert { " ".blank? } assert { " \n\t \r ".blank? } assert { " ".blank? } assert { "\u00a0".blank? } assert { " ".encode("UTF-16LE").blank? } end def test_not_blank? assert { !"a".blank? } assert { !"my value".encode("UTF-16LE").blank? } end end magnus-0.7.1/src/api.rs000064400000000000000000000144201046102023000130320ustar 00000000000000//! This module/file's name is a hack to get the `impl Ruby` defined here to //! show first in docs. This module shouldn't be exposed publicly. use std::{cell::RefCell, marker::PhantomData}; use rb_sys::ruby_native_thread_p; // Ruby does not expose this publicly, but it is used in the fiddle gem via // this kind of hack, and although the function is marked experimental in // Ruby's source, that comment and the code have been unchanged singe 1.9.2, // 14 years ago as of writing. extern "C" { fn ruby_thread_has_gvl_p() -> ::std::os::raw::c_int; } use crate::{error::RubyUnavailableError, value::ReprValue}; #[derive(Clone, Copy)] enum RubyGvlState { Locked, Unlocked, NonRubyThread, } thread_local! { static RUBY_GVL_STATE: RefCell> = RefCell::new(None); } impl RubyGvlState { fn current() -> Self { let current = if unsafe { ruby_thread_has_gvl_p() } != 0 { Self::Locked } else if unsafe { ruby_native_thread_p() != 0 } { Self::Unlocked } else { Self::NonRubyThread }; RUBY_GVL_STATE.with(|ruby_gvl_state| { *ruby_gvl_state.borrow_mut() = Some(current); }); current } fn cached() -> Self { RUBY_GVL_STATE.with(|ruby_gvl_state| { let x = *ruby_gvl_state.borrow(); match x { // assumed not to change because there's currently no api to // unlock. Some(Self::Locked) => Self::Locked, None => Self::current(), // Don't expect without an api to unlock, so skip cache Some(Self::Unlocked) => Self::current(), // assumed not to change Some(Self::NonRubyThread) => Self::NonRubyThread, } }) } fn ok(self, value: T) -> Result { match self { Self::Locked => Ok(value), Self::Unlocked => Err(RubyUnavailableError::GvlUnlocked), Self::NonRubyThread => Err(RubyUnavailableError::NonRubyThread), } } } /// A handle to access Ruby's API. /// /// Using Ruby's API requires the Ruby VM to be initalised and all access to be /// from a Ruby-created thread. /// /// This structure allows safe access to Ruby's API as it should only be /// possible to aquire an instance in situations where Ruby's API is known to /// be available. /// /// Many functions that take Ruby values as arguments are available directly /// without having to use a `Ruby` handle, as being able to provide a Ruby /// value is 'proof' the function is being called from a Ruby thread. Because /// of this most methods defined on `Ruby` deal with creating Ruby objects /// from Rust data. /// /// --- /// /// The methods available on `Ruby` are broken up into sections for easier /// navigation. /// /// * [Accessing `Ruby`](#accessing-ruby) - how to get a `Ruby` handle /// * [Argument Parsing](#argument-parsing) - helpers for argument handling /// * [Blocks](#blocks) - working with Ruby blocks /// * [Conversion to `Value`](#conversion-to-value) /// * [Core Classes](#core-classes) - access built-in classes /// * [Core Exceptions](#core-exceptions) - access built-in exceptions /// * [Core Modules](#core-modules) - access built-in modules /// * [Embedding](#embedding) - functions relevant when embedding Ruby in Rust /// * [`Encoding`](#encoding) - string encoding /// * [Encoding Index](#encoding-index) - string encoding /// * [Errors](#errors) /// * [Extracting values from `Opaque`/`Lazy`](#extracting-values-from-opaquelazy) /// * [`false`](#false) /// * [`Fiber`](#fiber) /// * [`Fixnum`](#fixnum) - small/fast integers /// * [`Float`](#float) /// * [`Flonum`](#flonum) - lower precision/fast floats /// * [`GC`](#gc) - Garbage Collection /// * [Globals](#globals) - global variables, etc, plus current VM state such /// as calling the current `super` method. /// * [`Id`](#id) - low-level Symbol representation /// * [`Integer`](#integer) /// * [`Mutex`](#mutex) /// * [`nil`](#nil) /// * [`Proc`](#proc) - Ruby's blocks as objects /// * [`Process`](#process) - external processes /// * [`Range`](#range) /// * [`RArray`](#rarray) /// * [`RbEncoding`](#rbencoding) - string encoding /// * [`RBignum`](#rbignum) - big integers /// * [`RFloat`](#rfloat) /// * [`RHash`](#rhash) /// * [`RModule`](#rmodule) /// * [`RRational`](#rrational) /// * [`RRegexp`](#rregexp) /// * [`RString`](#rstring) /// * [`RTypedData`](#rtypeddata) - wrapping Rust data in a Ruby object /// * [`StaticSymbol`](#staticsymbol) - non GC'd symbols /// * [`Struct`](#struct) /// * [`Symbol`](#symbol) /// * [`Thread`](#thread) /// * [`Time`](#time) /// * [`true`](#true) /// * [`typed_data::Obj`](#typed_dataobj) - wrapping Rust data in a Ruby object pub struct Ruby(PhantomData<*mut ()>); /// # Accessing `Ruby` /// /// These functions allow you to obtain a `Ruby` handle only when the current /// thread is a Ruby thread. /// /// Methods exposed to Ruby via the [`method`](macro@crate::method), /// [`function`](macro@crate::function) or [`init`](macro@crate::init) macros /// can also take an optional first argument of `&Ruby` to obtain a `Ruby` /// handle. impl Ruby { /// Get a handle to Ruby's API. /// /// Returns a new handle to Ruby's API if it can be verified the current /// thread is a Ruby thread. /// /// If the Ruby API is not useable, returns `Err(RubyUnavailableError)`. pub fn get() -> Result { RubyGvlState::cached().ok(Self(PhantomData)) } /// Get a handle to Ruby's API. /// /// Returns a new handle to Ruby's API using a Ruby value as proof that the /// current thread is a Ruby thread. /// /// Note that all Ruby values are [`Copy`], so this will not take ownership /// of the passed value. #[allow(unused_variables)] pub fn get_with(value: T) -> Self where T: ReprValue, { Self(PhantomData) } /// Get a handle to Ruby's API. /// /// # Safety /// /// This must only be called from a Ruby thread - that is one created by /// Ruby, or the main thread after [`embed::init`](crate::embed::init) has /// been called - and without having released the GVL. #[inline] pub unsafe fn get_unchecked() -> Self { Self(PhantomData) } } magnus-0.7.1/src/block.rs000064400000000000000000001111341046102023000133530ustar 00000000000000//! Types and functions for working with Ruby blocks and Procs. //! //! See also [`Ruby`](Ruby#blocks) for more block related methods. use std::{ fmt, mem::{forget, size_of}, os::raw::c_int, slice, }; use rb_sys::{ rb_block_given_p, rb_block_proc, rb_data_typed_object_wrap, rb_obj_is_proc, rb_proc_arity, rb_proc_call_kw, rb_proc_lambda_p, rb_proc_new, rb_yield, rb_yield_splat, rb_yield_values_kw, VALUE, }; use crate::{ data_type_builder, enumerator::Enumerator, error::{ensure, protect, Error}, gc, into_value::{kw_splat, ArgList, IntoValue, RArrayArgList}, method::{Block, BlockReturn}, object::Object, r_array::RArray, try_convert::TryConvert, typed_data::{DataType, DataTypeFunctions}, value::{ private::{self, ReprValue as _}, NonZeroValue, ReprValue, Value, }, Ruby, }; /// # `Proc` /// /// Functions that can be used to create instances of [`Proc`], Ruby's /// representation of a block as an object. /// /// See also the [`Proc`] type. impl Ruby { /// Create a new `Proc`. /// /// As `block` is a function pointer, only functions and closures that do /// not capture any variables are permitted. For more flexibility (at the /// cost of allocating) see [`proc_from_fn`](Ruby::proc_from_fn). /// /// # Examples /// /// ``` /// use magnus::{prelude::*, rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let proc = ruby.proc_new(|_ruby, args, _block| { /// let acc = i64::try_convert(*args.get(0).unwrap())?; /// let i = i64::try_convert(*args.get(1).unwrap())?; /// Ok(acc + i) /// }); /// /// rb_assert!(ruby, "proc.call(1, 2) == 3", proc); /// /// rb_assert!(ruby, "[1, 2, 3, 4, 5].inject(&proc) == 15", proc); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn proc_new(&self, block: fn(&Ruby, &[Value], Option) -> R) -> Proc where R: BlockReturn, { unsafe extern "C" fn call( _yielded_arg: VALUE, callback_arg: VALUE, argc: c_int, argv: *const VALUE, blockarg: VALUE, ) -> VALUE where R: BlockReturn, { let func = std::mem::transmute::) -> R>(callback_arg); func.call_handle_error(argc, argv as *const Value, Value::new(blockarg)) .as_rb_value() } let call_func = call:: as unsafe extern "C" fn(VALUE, VALUE, c_int, *const VALUE, VALUE) -> VALUE; unsafe { #[allow(clippy::fn_to_numeric_cast)] Proc::from_rb_value_unchecked(rb_proc_new(Some(call_func), block as VALUE)) } } /// Create a new `Proc`. /// /// See also [`proc_new`](Ruby::proc_new), which is more efficient when /// `block` is a function or closure that does not capture any variables. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let mut count = 0; /// /// let proc = ruby.proc_from_fn(move |_ruby, args, _block| { /// let step = i64::try_convert(*args.get(0).unwrap())?; /// count += step; /// Ok(count) /// }); /// /// rb_assert!(ruby, "proc.call(1) == 1", proc); /// rb_assert!(ruby, "proc.call(1) == 2", proc); /// rb_assert!(ruby, "proc.call(2) == 4", proc); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn proc_from_fn(&self, block: F) -> Proc where F: 'static + Send + FnMut(&Ruby, &[Value], Option) -> R, R: BlockReturn, { unsafe extern "C" fn call( _yielded_arg: VALUE, callback_arg: VALUE, argc: c_int, argv: *const VALUE, blockarg: VALUE, ) -> VALUE where F: FnMut(&Ruby, &[Value], Option) -> R, R: BlockReturn, { let closure = &mut *(callback_arg as *mut F); closure .call_handle_error(argc, argv as *const Value, Value::new(blockarg)) .as_rb_value() } let (closure, keepalive) = wrap_closure(block); let call_func = call:: as unsafe extern "C" fn(VALUE, VALUE, c_int, *const VALUE, VALUE) -> VALUE; let proc = unsafe { Proc::from_rb_value_unchecked(rb_proc_new(Some(call_func), closure as VALUE)) }; // ivar without @ prefix is invisible from Ruby proc.ivar_set("__rust_closure", keepalive).unwrap(); proc } } /// Wrapper type for a Value known to be an instance of Ruby’s Proc class. /// /// See the [`ReprValue`] and [`Object`] traits for additional methods /// available on this type. See [`Ruby`](Ruby#proc) for methods to create a /// `Proc`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct Proc(NonZeroValue); impl Proc { /// Return `Some(Proc)` if `val` is a `Proc`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{block::Proc, eval, Value}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let val: Value = eval("Proc.new {|a, b| a + b}").unwrap(); /// assert!(Proc::from_value(val).is_some()); /// /// let val: Value = eval("1 + 2").unwrap(); /// assert!(Proc::from_value(val).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { Value::new(rb_obj_is_proc(val.as_rb_value())) .to_bool() .then(|| Self(NonZeroValue::new_unchecked(val))) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } /// Create a new `Proc`. /// /// As `block` is a function pointer, only functions and closures that do /// not capture any variables are permitted. For more flexibility (at the /// cost of allocating) see [`from_fn`](Proc::from_fn). /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::proc_new`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{block::Proc, prelude::*, rb_assert}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let proc = Proc::new(|_ruby, args, _block| { /// let acc = i64::try_convert(*args.get(0).unwrap())?; /// let i = i64::try_convert(*args.get(1).unwrap())?; /// Ok(acc + i) /// }); /// /// rb_assert!("proc.call(1, 2) == 3", proc); /// /// rb_assert!("[1, 2, 3, 4, 5].inject(&proc) == 15", proc); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::proc_new` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn new(block: fn(&Ruby, &[Value], Option) -> R) -> Self where R: BlockReturn, { get_ruby!().proc_new(block) } /// Create a new `Proc`. /// /// See also [`Proc::new`], which is more efficient when `block` is a /// function or closure that does not capture any variables. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::proc_from_fn`] for /// the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{block::Proc, prelude::*, rb_assert}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let mut count = 0; /// /// let proc = Proc::from_fn(move |_ruby, args, _block| { /// let step = i64::try_convert(*args.get(0).unwrap())?; /// count += step; /// Ok(count) /// }); /// /// rb_assert!("proc.call(1) == 1", proc); /// rb_assert!("proc.call(1) == 2", proc); /// rb_assert!("proc.call(2) == 4", proc); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::proc_from_fn` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn from_fn(block: F) -> Self where F: 'static + Send + FnMut(&Ruby, &[Value], Option) -> R, R: BlockReturn, { get_ruby!().proc_from_fn(block) } /// Call the proc with `args`. /// /// Returns `Ok(T)` if the proc runs without error and the return value /// converts into a `T`, or returns `Err` if the proc raises or the /// conversion fails. /// /// # Examples /// /// ``` /// use magnus::{block::Proc, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let proc: Proc = ruby.eval("Proc.new {|a, b| a + b}").unwrap(); /// /// // call with a tuple /// let result: i64 = proc.call((1, 2)).unwrap(); /// assert_eq!(3, result); /// /// // call with a slice /// let result: i64 = proc /// .call(&[ruby.integer_from_i64(3), ruby.integer_from_i64(4)][..]) /// .unwrap(); /// assert_eq!(7, result); /// /// // call with an array /// let result: i64 = proc /// .call([ruby.integer_from_i64(5), ruby.integer_from_i64(6)]) /// .unwrap(); /// assert_eq!(11, result); /// /// // call with a Ruby array /// let array = ruby.ary_from_vec(vec![7, 8]); /// let result: i64 = proc.call(array).unwrap(); /// assert_eq!(15, result); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// With keyword arguments: /// /// ``` /// use magnus::{block::Proc, kwargs, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let proc: Proc = ruby.eval("Proc.new {|a, b:, c:| a + b + c}").unwrap(); /// /// let result: i64 = proc.call((1, kwargs!("b" => 2, "c" => 3))).unwrap(); /// assert_eq!(6, result); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// Ignoring return value: /// /// ``` /// use magnus::{block::Proc, rb_assert, Error, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let proc: Proc = ruby.eval("Proc.new { $called = true }").unwrap(); /// /// let _: Value = proc.call(()).unwrap(); /// /// rb_assert!(ruby, "$called == true"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn call(self, args: A) -> Result where A: RArrayArgList, T: TryConvert, { let kw_splat = kw_splat(&args); let args = args.into_array_arg_list_with(&Ruby::get_with(self)); unsafe { protect(|| { Value::new(rb_proc_call_kw( self.as_rb_value(), args.as_rb_value(), kw_splat as c_int, )) }) .and_then(TryConvert::try_convert) } } /// Returns the number of arguments `self` takes. /// /// If `self` takes no arguments, returns `0`. /// If `self` takes only required arguments, returns the number of required /// arguments. /// If `self` is a lambda and has optional arguments, or is not a lambda /// and takes a splat argument, returns `-n-1`, where `n` is the number of /// required arguments. /// If `self` is not a lambda, and takes a finite number of optional /// arguments, returns the number of required arguments. /// Keyword arguments are considered as a single additional argument, that /// argument being required if any keyword argument is required. /// /// # Examples /// /// ``` /// use magnus::{block::Proc, eval}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let proc: Proc = eval("proc {nil}").unwrap(); /// assert_eq!(proc.arity(), 0); /// /// let proc: Proc = eval("proc {|a| a + 1}").unwrap(); /// assert_eq!(proc.arity(), 1); /// /// let proc: Proc = eval("proc {|a, b| a + b}").unwrap(); /// assert_eq!(proc.arity(), 2); /// /// let proc: Proc = eval("proc {|*args| args.sum}").unwrap(); /// assert_eq!(proc.arity(), -1); /// ``` pub fn arity(self) -> i64 { unsafe { rb_proc_arity(self.as_rb_value()) as i64 } } /// Returns whether or not `self` is a lambda. /// /// # Examples /// /// ``` /// use magnus::{block::Proc, eval}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let proc: Proc = eval("proc {|a, b| a + b}").unwrap(); /// assert!(!proc.is_lambda()); /// /// let proc: Proc = eval("lambda {|a, b| a + b}").unwrap(); /// assert!(proc.is_lambda()); /// ``` pub fn is_lambda(self) -> bool { unsafe { Value::new(rb_proc_lambda_p(self.as_rb_value())).to_bool() } } } impl fmt::Display for Proc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for Proc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for Proc { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl Object for Proc {} unsafe impl private::ReprValue for Proc {} impl ReprValue for Proc {} impl TryConvert for Proc { fn try_convert(val: Value) -> Result { let handle = Ruby::get_with(val); if let Some(p) = Proc::from_value(val) { return Ok(p); } let p_val: Value = match val.funcall("to_proc", ()) { Ok(v) => v, Err(_) => { return Err(Error::new( handle.exception_type_error(), format!("no implicit conversion of {} into Proc", unsafe { val.classname() },), )) } }; Proc::from_value(val).ok_or_else(|| { Error::new( handle.exception_type_error(), format!( "can't convert {0} to Proc ({0}#to_proc gives {1})", unsafe { val.classname() }, unsafe { p_val.classname() }, ), ) }) } } /// Wrap a closure in a Ruby object with no class. /// /// This effectivly makes the closure's lifetime managed by Ruby. It will be /// dropped when the returned `Value` is garbage collected. fn wrap_closure(func: F) -> (*mut F, Value) where F: FnMut(&Ruby, &[Value], Option) -> R, R: BlockReturn, { struct Closure(F, DataType); unsafe impl Send for Closure {} impl DataTypeFunctions for Closure { fn mark(&self, marker: &gc::Marker) { // Attempt to mark any Ruby values captured in a closure. // Rust's closures are structs that contain all the values they // have captured. This reads that struct as a slice of VALUEs and // calls rb_gc_mark_locations which calls gc_mark_maybe which // marks VALUEs and ignores non-VALUEs marker.mark_slice(unsafe { slice::from_raw_parts( &self.0 as *const _ as *const Value, size_of::() / size_of::(), ) }); } } let data_type = data_type_builder!(Closure, "rust closure") .free_immediately() .mark() .build(); let boxed = Box::new(Closure(func, data_type)); let ptr = Box::into_raw(boxed); let value = unsafe { Value::new(rb_data_typed_object_wrap( 0, // using 0 for the class will hide the object from ObjectSpace ptr as *mut _, (*ptr).1.as_rb_data_type() as *const _, )) }; unsafe { (&mut (*ptr).0 as *mut F, value) } } /// # Blocks /// /// Functions to enable working with Ruby blocks. /// /// See also the [`block`](self) module. impl Ruby { /// Returns whether a Ruby block has been supplied to the current method. /// /// # Examples /// /// ``` /// use magnus::{function, rb_assert, Error, Ruby}; /// /// fn got_block(ruby: &Ruby) -> bool { /// ruby.block_given() /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_function("got_block?", function!(got_block, 0)); /// /// rb_assert!(ruby, "got_block? {} == true"); /// rb_assert!(ruby, "got_block? == false"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn block_given(&self) -> bool { unsafe { rb_block_given_p() != 0 } } /// Returns the block given to the current method as a [`Proc`] instance. /// /// # Examples /// /// ``` /// use magnus::{block::Proc, function, rb_assert, Error, Ruby}; /// /// fn make_proc(ruby: &Ruby) -> Result { /// ruby.block_proc() /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_function("make_proc", function!(make_proc, 0)); /// /// rb_assert!(ruby, "make_proc {}.is_a?(Proc)"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn block_proc(&self) -> Result { let val = unsafe { protect(|| Value::new(rb_block_proc()))? }; Ok(Proc::from_value(val).unwrap()) } /// Yields a value to the block given to the current method. /// /// **Note:** A method using `yield_value` converted to an Enumerator with /// `to_enum`/[`Value::enumeratorize`] will result in a non-functional /// Enumerator on versions of Ruby before 3.1. See [`Yield`] for an /// alternative. /// /// # Examples /// /// ``` /// use magnus::{function, rb_assert, Error, Ruby, Value}; /// /// fn metasyntactic_variables(ruby: &Ruby) -> Result<(), Error> { /// let _: Value = ruby.yield_value("foo")?; /// let _: Value = ruby.yield_value("bar")?; /// let _: Value = ruby.yield_value("baz")?; /// Ok(()) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_function( /// "metasyntactic_variables", /// function!(metasyntactic_variables, 0), /// ); /// /// let vars = ruby.ary_new(); /// rb_assert!( /// ruby, /// "metasyntactic_variables {|var| vars << var} == nil", /// vars /// ); /// rb_assert!(ruby, r#"vars == ["foo", "bar", "baz"]"#, vars); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn yield_value(&self, val: T) -> Result where T: IntoValue, U: TryConvert, { let val = self.into_value(val); unsafe { protect(|| Value::new(rb_yield(val.as_rb_value()))).and_then(TryConvert::try_convert) } } /// Yields multiple values to the block given to the current method. /// /// **Note:** A method using `yield_values` converted to an Enumerator with /// `to_enum`/[`Value::enumeratorize`] will result in a non-functional /// Enumerator on versions of Ruby before 3.1. See [`YieldValues`] for an /// alternative. /// /// # Examples /// /// ``` /// use magnus::{function, kwargs, rb_assert, Error, Ruby, Value}; /// /// fn metasyntactic_variables(ruby: &Ruby) -> Result<(), Error> { /// let _: Value = ruby.yield_values((0, kwargs!("var" => "foo")))?; /// let _: Value = ruby.yield_values((1, kwargs!("var" => "bar")))?; /// let _: Value = ruby.yield_values((2, kwargs!("var" => "baz")))?; /// Ok(()) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_function( /// "metasyntactic_variables", /// function!(metasyntactic_variables, 0), /// ); /// /// let vars = ruby.ary_new(); /// rb_assert!( /// ruby, /// "metasyntactic_variables {|pos, var:| vars << [pos, var]} == nil", /// vars /// ); /// rb_assert!( /// ruby, /// r#"vars == [[0, "foo"], [1, "bar"], [2, "baz"]]"#, /// vars /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn yield_values(&self, vals: T) -> Result where T: ArgList, U: TryConvert, { let kw_splat = kw_splat(&vals); let vals = vals.into_arg_list_with(self); let slice = vals.as_ref(); unsafe { protect(|| { Value::new(rb_yield_values_kw( slice.len() as c_int, slice.as_ptr() as *const VALUE, kw_splat as c_int, )) }) .and_then(TryConvert::try_convert) } } /// Yields a Ruby Array to the block given to the current method. /// /// **Note:** A method using `yield_splat` converted to an Enumerator with /// `to_enum`/[`Value::enumeratorize`] will result in a non-functional /// Enumerator on versions of Ruby before 3.1. See [`YieldSplat`] for an /// alternative. /// /// # Examples /// /// ``` /// use magnus::{function, rb_assert, Error, Ruby, Value}; /// /// fn metasyntactic_variables(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_new(); /// ary.push(0)?; /// ary.push("foo")?; /// let _: Value = ruby.yield_splat(ary)?; /// let ary = ruby.ary_new(); /// ary.push(1)?; /// ary.push("bar")?; /// let _: Value = ruby.yield_splat(ary)?; /// let ary = ruby.ary_new(); /// ary.push(2)?; /// ary.push("baz")?; /// let _: Value = ruby.yield_splat(ary)?; /// Ok(()) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_function( /// "metasyntactic_variables", /// function!(metasyntactic_variables, 0), /// ); /// /// let vars = ruby.ary_new(); /// rb_assert!( /// ruby, /// "metasyntactic_variables {|pos, var| vars << [pos, var]} == nil", /// vars /// ); /// rb_assert!( /// ruby, /// r#"vars == [[0, "foo"], [1, "bar"], [2, "baz"]]"#, /// vars /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn yield_splat(&self, vals: RArray) -> Result where T: TryConvert, { unsafe { protect(|| Value::new(rb_yield_splat(vals.as_rb_value()))) .and_then(TryConvert::try_convert) } } } /// Returns whether a Ruby block has been supplied to the current method. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::block_given`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{block::block_given, define_global_function, function, rb_assert}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// fn got_block() -> bool { /// block_given() /// } /// /// define_global_function("got_block?", function!(got_block, 0)); /// /// rb_assert!("got_block? {} == true"); /// rb_assert!("got_block? == false"); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::block_given` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn block_given() -> bool { get_ruby!().block_given() } /// Returns the block given to the current method as a [`Proc`] instance. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::block_proc`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{ /// block::{block_proc, Proc}, /// define_global_function, function, rb_assert, Error, /// }; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// fn make_proc() -> Result { /// block_proc() /// } /// /// define_global_function("make_proc", function!(make_proc, 0)); /// /// rb_assert!("make_proc {}.is_a?(Proc)"); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::block_proc` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn block_proc() -> Result { get_ruby!().block_proc() } /// Yields a value to the block given to the current method. /// /// **Note:** A method using `yield_value` converted to an Enumerator with /// `to_enum`/[`Value::enumeratorize`] will result in a non-functional /// Enumerator on versions of Ruby before 3.1. See [`Yield`] for an /// alternative. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::yield_value`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{ /// block::yield_value, define_global_function, function, rb_assert, Error, RArray, Value, /// }; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// fn metasyntactic_variables() -> Result<(), Error> { /// let _: Value = yield_value("foo")?; /// let _: Value = yield_value("bar")?; /// let _: Value = yield_value("baz")?; /// Ok(()) /// } /// /// define_global_function( /// "metasyntactic_variables", /// function!(metasyntactic_variables, 0), /// ); /// /// let vars = RArray::new(); /// rb_assert!("metasyntactic_variables {|var| vars << var} == nil", vars); /// rb_assert!(r#"vars == ["foo", "bar", "baz"]"#, vars); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::yield_value` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn yield_value(val: T) -> Result where T: IntoValue, U: TryConvert, { get_ruby!().yield_value(val) } /// Yields multiple values to the block given to the current method. /// /// **Note:** A method using `yield_values` converted to an Enumerator with /// `to_enum`/[`Value::enumeratorize`] will result in a non-functional /// Enumerator on versions of Ruby before 3.1. See [`YieldValues`] for an /// alternative. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::yield_values`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{ /// block::yield_values, define_global_function, function, rb_assert, Error, RArray, Value, /// }; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// fn metasyntactic_variables() -> Result<(), Error> { /// let _: Value = yield_values((0, "foo"))?; /// let _: Value = yield_values((1, "bar"))?; /// let _: Value = yield_values((2, "baz"))?; /// Ok(()) /// } /// /// define_global_function( /// "metasyntactic_variables", /// function!(metasyntactic_variables, 0), /// ); /// /// let vars = RArray::new(); /// rb_assert!( /// "metasyntactic_variables {|pos, var| vars << [pos, var]} == nil", /// vars /// ); /// rb_assert!(r#"vars == [[0, "foo"], [1, "bar"], [2, "baz"]]"#, vars); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::yield_values` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn yield_values(vals: T) -> Result where T: ArgList, U: TryConvert, { get_ruby!().yield_values(vals) } /// Yields a Ruby Array to the block given to the current method. /// /// **Note:** A method using `yield_splat` converted to an Enumerator with /// `to_enum`/[`Value::enumeratorize`] will result in a non-functional /// Enumerator on versions of Ruby before 3.1. See [`YieldSplat`] for an /// alternative. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::yield_splat`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{ /// block::yield_splat, define_global_function, function, rb_assert, Error, RArray, Value, /// }; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// fn metasyntactic_variables() -> Result<(), Error> { /// let ary = RArray::new(); /// ary.push(0)?; /// ary.push("foo")?; /// let _: Value = yield_splat(ary)?; /// let ary = RArray::new(); /// ary.push(1)?; /// ary.push("bar")?; /// let _: Value = yield_splat(ary)?; /// let ary = RArray::new(); /// ary.push(2)?; /// ary.push("baz")?; /// let _: Value = yield_splat(ary)?; /// Ok(()) /// } /// /// define_global_function( /// "metasyntactic_variables", /// function!(metasyntactic_variables, 0), /// ); /// /// let vars = RArray::new(); /// rb_assert!( /// "metasyntactic_variables {|pos, var| vars << [pos, var]} == nil", /// vars /// ); /// rb_assert!(r#"vars == [[0, "foo"], [1, "bar"], [2, "baz"]]"#, vars); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::yield_splat` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn yield_splat(vals: RArray) -> Result where T: TryConvert, { get_ruby!().yield_splat(vals) } // Our regular implementation of `yield` breaks yielding methods being // converted to Enumerators because of the protect call not being compatible // with the fibers used in Ruby itself to implement `Enumerator#next`. // We have to use protect in `yield` because otherwise Ruby code can // `break`/`return` through Rust code and break Rust invariants. // This gives up using `protect` by instead using `ensure`, not exposing the // `yield` call to user code, and maintaining the invariants ourselves. As it // can still be `brake`/`return`ed though it can't be public as it's only safe // to call as the last thing in one of our method wrappers (where the raise // would normally go). Returning an iterator from a method will trigger this. pub(crate) unsafe fn do_yield_iter(mut iter: I) where I: Iterator, T: IntoValue, { let handle = Ruby::get_unchecked(); let ptr = &mut iter as *mut I; forget(iter); // we're going to drop this ourself; // ensure runs the first closure, but yield may raise, so the first // closure might never reach the end, so wouldn't drop. The second // closure is always run, and always after the first, so we do the // drop there ensure( || { for val in &mut *ptr { rb_yield(handle.into_value(val).as_rb_value()); } handle.qnil() }, || { ptr.drop_in_place(); }, ); } // see do_yield_iter pub(crate) unsafe fn do_yield_values_iter(mut iter: I) where I: Iterator, T: ArgList, { let handle = Ruby::get_unchecked(); let ptr = &mut iter as *mut I; forget(iter); ensure( || { for val in &mut *ptr { let kw_splat = kw_splat(&val); let vals = val.into_arg_list_with(&handle); let slice = vals.as_ref(); rb_yield_values_kw( slice.len() as c_int, slice.as_ptr() as *const VALUE, kw_splat as c_int, ); } handle.qnil() }, || { ptr.drop_in_place(); }, ); } // see do_yield_iter pub(crate) unsafe fn do_yield_splat_iter(mut iter: I) where I: Iterator, { let ptr = &mut iter as *mut I; forget(iter); ensure( || { for val in &mut *ptr { rb_yield_splat(val.as_rb_value()); } Ruby::get_unchecked().qnil() }, || { ptr.drop_in_place(); }, ); } /// Helper type for functions that either yield a single value to a block or /// return an Enumerator. /// /// `I` must implement `Iterator`, where `T` implements [`IntoValue`]. /// /// # Examples /// /// ``` /// use magnus::{block::Yield, method, prelude::*, rb_assert, Error, Ruby, Value}; /// /// fn count_to_3(ruby: &Ruby, rb_self: Value) -> Yield> { /// if ruby.block_given() { /// Yield::Iter(1..=3) /// } else { /// Yield::Enumerator(rb_self.enumeratorize("count_to_3", ())) /// } /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_function("count_to_3", method!(count_to_3, 0)); /// /// // call Ruby method with a block. /// let a = ruby.ary_new(); /// rb_assert!(ruby, "count_to_3 {|i| a << i} == nil", a); /// rb_assert!(ruby, "a == [1, 2, 3]", a); /// /// // call Ruby method without a block. /// let enumerator: Value = ruby.eval("count_to_3").unwrap(); /// /// rb_assert!(ruby, "enumerator.next == 1", enumerator); /// rb_assert!(ruby, "enumerator.next == 2", enumerator); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub enum Yield { /// Yields `I::Item` to given block. Iter(I), /// Returns `Enumerator` from the method. Enumerator(Enumerator), } /// Helper type for functions that either yield multiple values to a block or /// return an Enumerator. /// /// `I` must implement `Iterator`, where `T` implements [`ArgList`]. /// /// # Examples /// /// ``` /// use magnus::{block::YieldValues, method, prelude::*, rb_assert, Error, Ruby, Value}; /// /// fn count_to_3_abc( /// ruby: &Ruby, /// rb_self: Value, /// ) -> YieldValues> { /// if ruby.block_given() { /// YieldValues::Iter((1..=3).zip('a'..='c')) /// } else { /// YieldValues::Enumerator(rb_self.enumeratorize("count_to_3_abc", ())) /// } /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_function("count_to_3_abc", method!(count_to_3_abc, 0)); /// /// // call Ruby method with a block. /// let a = ruby.ary_new(); /// rb_assert!(ruby, "count_to_3_abc {|i, c| a << [i, c]} == nil", a); /// rb_assert!(ruby, r#"a == [[1, "a"], [2, "b"], [3, "c"]]"#, a); /// /// // call Ruby method without a block. /// let enumerator: Value = ruby.eval("count_to_3_abc").unwrap(); /// /// rb_assert!(ruby, r#"enumerator.next == [1, "a"]"#, enumerator); /// rb_assert!(ruby, r#"enumerator.next == [2, "b"]"#, enumerator); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub enum YieldValues { /// Yields `I::Item` to given block. Iter(I), /// Returns `Enumerator` from the method. Enumerator(Enumerator), } /// Helper type for functions that either yield an array to a block or /// return an Enumerator. /// /// `I` must implement `Iterator`. /// /// # Examples /// /// ``` /// use magnus::{block::YieldSplat, method, prelude::*, rb_assert, Error, RArray, Ruby, Value}; /// /// fn count_to_3_abc(ruby: &Ruby, rb_self: Value) -> YieldSplat> { /// if ruby.block_given() { /// YieldSplat::Iter((1..=3).zip('a'..='c').map(|(i, c)| { /// // we know this will be called on a Ruby thread so it's safe /// // to get a handle to Ruby, but we don't want to be tied to the /// // lifetime of the existing `ruby`. /// let ary = unsafe { Ruby::get_unchecked() }.ary_new(); /// ary.push(i).unwrap(); /// ary.push(c).unwrap(); /// ary /// })) /// } else { /// YieldSplat::Enumerator(rb_self.enumeratorize("count_to_3_abc", ())) /// } /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_function("count_to_3_abc", method!(count_to_3_abc, 0)); /// /// // call Ruby method with a block. /// let a = ruby.ary_new(); /// rb_assert!(ruby, "count_to_3_abc {|i, c| a << [i, c]} == nil", a); /// rb_assert!(ruby, r#"a == [[1, "a"], [2, "b"], [3, "c"]]"#, a); /// /// // call Ruby method without a block. /// let enumerator: Value = ruby.eval("count_to_3_abc").unwrap(); /// /// rb_assert!(ruby, r#"enumerator.next == [1, "a"]"#, enumerator); /// rb_assert!(ruby, r#"enumerator.next == [2, "b"]"#, enumerator); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub enum YieldSplat { /// Yields `I::Item` to given block. Iter(I), /// Returns `Enumerator` from the method. Enumerator(Enumerator), } magnus-0.7.1/src/class.rs000064400000000000000000001374611046102023000134010ustar 00000000000000//! Types and functions for working with Ruby classes. //! //! See also [`Ruby`](Ruby#core-classes) for more class related methods. use std::{borrow::Cow, ffi::CStr, fmt, mem::transmute, os::raw::c_int}; #[cfg(ruby_gte_3_1)] use rb_sys::rb_cRefinement; use rb_sys::{ self, rb_alloc_func_t, rb_cArray, rb_cBasicObject, rb_cBinding, rb_cClass, rb_cComplex, rb_cDir, rb_cEncoding, rb_cEnumerator, rb_cFalseClass, rb_cFile, rb_cFloat, rb_cHash, rb_cIO, rb_cInteger, rb_cMatch, rb_cMethod, rb_cModule, rb_cNameErrorMesg, rb_cNilClass, rb_cNumeric, rb_cObject, rb_cProc, rb_cRandom, rb_cRange, rb_cRational, rb_cRegexp, rb_cStat, rb_cString, rb_cStruct, rb_cSymbol, rb_cThread, rb_cTime, rb_cTrueClass, rb_cUnboundMethod, rb_class2name, rb_class_new, rb_class_new_instance_kw, rb_class_superclass, rb_define_alloc_func, rb_get_alloc_func, rb_obj_alloc, rb_undef_alloc_func, ruby_value_type, VALUE, }; use crate::{ error::{protect, Error}, into_value::{kw_splat, ArgList, IntoValue}, module::Module, object::Object, try_convert::TryConvert, typed_data::TypedData, value::{ private::{self, ReprValue as _}, NonZeroValue, ReprValue, Value, }, Ruby, }; /// A Value pointer to a RClass struct, Ruby's internal representation of /// classes. /// /// See the [`Class`] trait for methods available on classes. /// See the [`Module`] trait for defining instance methods and nested /// classes/modules. /// See the [`Object`] trait for defining singlton methods (aka class methods). /// /// See the [`ReprValue`] trait for additional methods available on this type. #[derive(Clone, Copy)] #[repr(transparent)] pub struct RClass(NonZeroValue); impl RClass { /// Return `Some(RClass)` if `val` is a `RClass`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, RClass}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(RClass::from_value(eval("String").unwrap()).is_some()); /// assert!(RClass::from_value(eval("Enumerable").unwrap()).is_none()); /// assert!(RClass::from_value(eval("nil").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { (val.rb_type() == ruby_value_type::RUBY_T_CLASS) .then(|| Self(NonZeroValue::new_unchecked(val))) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } } impl fmt::Display for RClass { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for RClass { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for RClass { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl Object for RClass {} impl Module for RClass {} unsafe impl private::ReprValue for RClass {} impl ReprValue for RClass {} impl TryConvert for RClass { fn try_convert(val: Value) -> Result { match Self::from_value(val) { Some(v) => Ok(v), None => Err(Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into Class", unsafe { val.classname() },), )), } } } /// Functions available on all types representing a Ruby class. pub trait Class: Module { /// The type representing an instance of the class `Self`. type Instance; /// Create a new anonymous class. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, RClass, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let class = RClass::new(ruby.class_object())?; /// assert!(class.is_kind_of(ruby.class_class())); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{prelude::*, Error, ExceptionClass, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ExceptionClass::new(ruby.exception_standard_error()).is_ok()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn new(superclass: Self) -> Result; /// Create a new object, an instance of `self`, passing the arguments /// `args` to the initialiser. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.class_string().new_instance(())?; /// assert!(s.is_kind_of(ruby.class_string())); /// assert_eq!(s.to_string(), ""); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{eval, kwargs, prelude::*, Error, RClass, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let cls: RClass = eval!( /// ruby, /// r#" /// class Foo /// def initialize(bar, baz:) /// @bar = bar /// @baz = baz /// end /// /// attr_reader(:bar, :baz) /// end /// /// Object.const_get(:Foo) /// "# /// )?; /// let instance = cls.new_instance((1, kwargs!("baz" => 2)))?; /// assert!(instance.is_kind_of(cls)); /// let bar: i32 = instance.funcall("bar", ())?; /// assert_eq!(bar, 1); /// let baz: i32 = instance.funcall("baz", ())?; /// assert_eq!(baz, 2); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.exception_standard_error().new_instance(("bang!",))?; /// assert!(s.is_kind_of(ruby.exception_standard_error())); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{eval, ExceptionClass, kwargs, prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let exc: ExceptionClass = eval!( /// ruby, /// r#" /// class MyError < StandardError /// def initialize(message:) /// super(message) /// end /// end /// /// Object.const_get(:MyError) /// "# /// )?; /// let s = exc.new_instance((kwargs!("message" => "bang!"),))?; /// assert!(s.is_kind_of(exc)); /// let message: String = s.funcall("message", ())?; /// assert_eq!(message, "bang!"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn new_instance(self, args: T) -> Result where T: ArgList; /// Create a new object, an instance of `self`, without calling the class's /// `initialize` method. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.class_string().obj_alloc()?; /// assert!(s.is_kind_of(ruby.class_string())); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.exception_standard_error().obj_alloc()?; /// assert!(s.is_kind_of(ruby.exception_standard_error())); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn obj_alloc(self) -> Result; /// Returns the parent class of `self`. /// /// Returns `Err` if `self` can not have a parent class. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let klass = ruby.class_hash().superclass()?; /// assert!(klass.equal(ruby.class_object())?); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let klass = ruby.exception_exception().superclass()?; /// assert!(klass.equal(ruby.class_object())?); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn superclass(self) -> Result { protect(|| unsafe { RClass::from_rb_value_unchecked(rb_class_superclass(self.as_rb_value())) }) } /// Return the name of `self`. /// /// # Safety /// /// Ruby may modify or free the memory backing the returned str, the caller /// must ensure this does not happen. /// /// This can be used safely by immediately calling /// [`into_owned`](Cow::into_owned) on the return value. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let value = ruby.class_hash(); /// // safe as we neve give Ruby a chance to free the string. /// let s = unsafe { value.name() }.into_owned(); /// assert_eq!(s, "Hash"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let value = ruby.exception_standard_error(); /// // safe as we neve give Ruby a chance to free the string. /// let s = unsafe { value.name() }.into_owned(); /// assert_eq!(s, "StandardError"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` unsafe fn name(&self) -> Cow { let ptr = rb_class2name(self.as_rb_value()); let cstr = CStr::from_ptr(ptr); cstr.to_string_lossy() } /// Return `self` as an [`RClass`]. fn as_r_class(self) -> RClass { RClass::from_value(self.as_value()).unwrap() } /// Define an allocator function for `self` using `T`'s [`Default`] /// implementation. /// /// In Ruby creating a new object has two steps, first the object is /// allocated, and then it is initialised. Allocating the object is handled /// by the `new` class method, which then also calls `initialize` on the /// newly allocated object. /// /// This does not map well to Rust, where data is allocated and initialised /// in a single step. For this reason most examples in this documentation /// show defining the `new` class method directly, opting out of the two /// step allocate and then initialise process. However, this means the /// class can't be subclassed in Ruby. /// /// Defining an allocator function allows a class be subclassed with the /// normal Ruby behaviour of calling the `initialize` method. /// /// Be aware when creating an instance of once of a class with an allocator /// function from Rust it must be done with [`Class::new_instance`] to call /// the allocator and then the `initialize` method. /// /// # Panics /// /// Panics if `self` and `::class()` are not the same class. /// /// # Examples /// /// ``` /// use std::cell::RefCell; /// /// use magnus::{function, method, prelude::*, wrap, Error, RClass, Ruby, Value}; /// /// #[derive(Default)] /// struct Point { /// x: isize, /// y: isize, /// } /// /// #[derive(Default)] /// #[wrap(class = "Point")] /// struct MutPoint(RefCell); /// /// impl MutPoint { /// fn initialize(&self, x: isize, y: isize) { /// let mut this = self.0.borrow_mut(); /// this.x = x; /// this.y = y; /// } /// /// // bypasses initialize /// fn create(x: isize, y: isize) -> MutPoint { /// MutPoint(RefCell::new(Point { x, y })) /// } /// /// // calls initialize /// fn call_new(class: RClass, x: isize, y: isize) -> Result { /// class.new_instance((x, y)) /// } /// /// fn distance(&self, other: &MutPoint) -> f64 { /// let a = self.0.borrow(); /// let b = other.0.borrow(); /// (((b.x - a.x).pow(2) + (b.y - a.y).pow(2)) as f64).sqrt() /// } /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let class = ruby.define_class("Point", ruby.class_object())?; /// class.define_alloc_func::(); /// class.define_singleton_method("create", function!(MutPoint::create, 2))?; /// class.define_singleton_method("call_new", method!(MutPoint::call_new, 2))?; /// class.define_method("initialize", method!(MutPoint::initialize, 2))?; /// class.define_method("distance", method!(MutPoint::distance, 1))?; /// /// let d: f64 = ruby.eval( /// " /// class OffsetPoint < Point /// def initialize(offset, x, y) /// super(x + offset, y + offset) /// end /// end /// a = Point.new(1, 1) /// b = OffsetPoint.new(2, 3, 3) /// a.distance(b).round(2) /// ", /// )?; /// /// assert_eq!(d, 5.66); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn define_alloc_func(self) where T: Default + TypedData, { extern "C" fn allocate(class: RClass) -> Value { Ruby::get_with(class) .obj_wrap_as(T::default(), class) .as_value() } let class = T::class(&Ruby::get_with(self)); assert!( class.equal(self).unwrap_or(false), "{} does not match {}", self.as_value(), class ); unsafe { rb_define_alloc_func( self.as_rb_value(), Some(transmute(allocate:: as extern "C" fn(RClass) -> Value)), ) } } /// Remove the allocator function of a class if it is Ruby's default /// allocator function. /// /// Useful for RTypedData, where instances should not be allocated by /// the default allocate function. `#[derive(TypedData)]` and `#[wrap]` /// take care of undefining the allocator function, you do not need /// to use `undef_default_alloc_func` if you're using one of those. /// /// # Examples /// /// ``` /// use magnus::{Class, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let class = ruby.define_class("Point", ruby.class_object())?; /// /// class.undef_default_alloc_func(); /// /// let instance = class.new_instance(()); /// assert_eq!( /// "allocator undefined for Point", /// instance.err().unwrap().to_string() /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn undef_default_alloc_func(self) { static INIT: std::sync::Once = std::sync::Once::new(); static mut RB_CLASS_ALLOCATE_INSTANCE: rb_alloc_func_t = None; let rb_class_allocate_instance = unsafe { INIT.call_once(|| { RB_CLASS_ALLOCATE_INSTANCE = rb_get_alloc_func(Ruby::get_unchecked().class_object().as_rb_value()); }); RB_CLASS_ALLOCATE_INSTANCE }; unsafe { if rb_get_alloc_func(self.as_rb_value()) == rb_class_allocate_instance { rb_undef_alloc_func(self.as_rb_value()) } } } } impl Class for RClass { type Instance = Value; fn new(superclass: Self) -> Result { debug_assert_value!(superclass); let superclass = superclass.as_rb_value(); protect(|| unsafe { Self::from_rb_value_unchecked(rb_class_new(superclass)) }) } fn new_instance(self, args: T) -> Result where T: ArgList, { let kw_splat = kw_splat(&args); let args = args.into_arg_list_with(&Ruby::get_with(self)); let slice = args.as_ref(); unsafe { protect(|| { Value::new(rb_class_new_instance_kw( slice.len() as c_int, slice.as_ptr() as *const VALUE, self.as_rb_value(), kw_splat as c_int, )) }) } } fn obj_alloc(self) -> Result { unsafe { protect(|| Value::new(rb_obj_alloc(self.as_rb_value()))) } } fn as_r_class(self) -> RClass { self } } /// # Core Classes /// /// Functions to access Ruby's built-in classes. /// /// See also the [`class`](self) module. impl Ruby { /// Return Ruby's `Array` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Array", klass = ruby.class_array()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_array(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cArray) } } /// Return Ruby's `BasicObject` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == BasicObject", /// klass = ruby.class_basic_object() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_basic_object(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cBasicObject) } } /// Return Ruby's `Binding` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Binding", klass = ruby.class_binding()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_binding(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cBinding) } } /// Return Ruby's `Class` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Class", klass = ruby.class_class()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_class(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cClass) } } /// Return Ruby's `Complex` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Complex", klass = ruby.class_complex()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_complex(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cComplex) } } /// Return Ruby's `Dir` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Dir", klass = ruby.class_dir()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_dir(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cDir) } } /// Return Ruby's `Encoding` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Encoding", klass = ruby.class_encoding()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_encoding(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cEncoding) } } /// Return Ruby's `Enumerator` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Enumerator", klass = ruby.class_enumerator()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_enumerator(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cEnumerator) } } /// Return Ruby's `FalseClass` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == FalseClass", /// klass = ruby.class_false_class() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_false_class(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cFalseClass) } } /// Return Ruby's `File` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == File", klass = ruby.class_file()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_file(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cFile) } } /// Return Ruby's `Float` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Float", klass = ruby.class_float()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_float(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cFloat) } } /// Return Ruby's `Hash` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Hash", klass = ruby.class_hash()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_hash(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cHash) } } /// Return Ruby's `IO` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == IO", klass = ruby.class_io()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_io(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cIO) } } /// Return Ruby's `Integer` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Integer", klass = ruby.class_integer()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_integer(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cInteger) } } /// Return Ruby's `MatchData` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == MatchData", klass = ruby.class_match()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_match(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cMatch) } } /// Return Ruby's `Method` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Method", klass = ruby.class_method()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_method(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cMethod) } } /// Return Ruby's `Module` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Module", klass = ruby.class_module()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_module(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cModule) } } /// Return Ruby's `NameError::message` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// r#"klass.name == "NameError::message""#, /// klass = ruby.class_name_error_mesg() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_name_error_mesg(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cNameErrorMesg) } } /// Return Ruby's `NilClass` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == NilClass", klass = ruby.class_nil_class()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_nil_class(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cNilClass) } } /// Return Ruby's `Numeric` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Numeric", klass = ruby.class_numeric()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_numeric(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cNumeric) } } /// Return Ruby's `Object` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Object", klass = ruby.class_object()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_object(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cObject) } } /// Return Ruby's `Proc` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Proc", klass = ruby.class_proc()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_proc(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cProc) } } /// Return Ruby's `Random` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Random", klass = ruby.class_random()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_random(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cRandom) } } /// Return Ruby's `Range` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Range", klass = ruby.class_range()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_range(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cRange) } } /// Return Ruby's `Rational` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Rational", klass = ruby.class_rational()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_rational(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cRational) } } /// Return Ruby's `Refinement` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Refinement", klass = ruby.class_refinement()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[cfg(any(ruby_gte_3_1, docsrs))] #[cfg_attr(docsrs, doc(cfg(ruby_gte_3_1)))] #[inline] pub fn class_refinement(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cRefinement) } } /// Return Ruby's `Regexp` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Regexp", klass = ruby.class_regexp()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_regexp(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cRegexp) } } /// Return Ruby's `File::Stat` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == File::Stat", klass = ruby.class_stat()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_stat(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cStat) } } /// Return Ruby's `String` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == String", klass = ruby.class_string()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_string(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cString) } } /// Return Ruby's `Struct` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Struct", klass = ruby.class_struct()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_struct(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cStruct) } } /// Return Ruby's `Symbol` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Symbol", klass = ruby.class_symbol()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_symbol(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cSymbol) } } /// Return Ruby's `Thread` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Thread", klass = ruby.class_thread()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_thread(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cThread) } } /// Return Ruby's `Time` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == Time", klass = ruby.class_time()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_time(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cTime) } } /// Return Ruby's `TrueClass` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == TrueClass", klass = ruby.class_true_class()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_true_class(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cTrueClass) } } /// Return Ruby's `UnboundMethod` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == UnboundMethod", /// klass = ruby.class_unbound_method() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn class_unbound_method(&self) -> RClass { unsafe { RClass::from_rb_value_unchecked(rb_cUnboundMethod) } } } /// Return Ruby's `Array` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_array`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_array` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn array() -> RClass { get_ruby!().class_array() } /// Return Ruby's `BasicObject` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_basic_object`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_basic_object` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn basic_object() -> RClass { get_ruby!().class_basic_object() } /// Return Ruby's `Binding` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_binding`] for /// the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_binding` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn binding() -> RClass { get_ruby!().class_binding() } /// Return Ruby's `Class` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_class`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_class` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn class() -> RClass { get_ruby!().class_class() } /// Return Ruby's `Complex` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_complex`] for /// the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_complex` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn complex() -> RClass { get_ruby!().class_complex() } /// Return Ruby's `Dir` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_dir`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_dir` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn dir() -> RClass { get_ruby!().class_dir() } /// Return Ruby's `Encoding` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_encoding`] for /// the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_encoding` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn encoding() -> RClass { get_ruby!().class_encoding() } /// Return Ruby's `Enumerator` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_enumerator`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_enumerator` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn enumerator() -> RClass { get_ruby!().class_enumerator() } /// Return Ruby's `FalseClass` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_false_class`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_false_class` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn false_class() -> RClass { get_ruby!().class_false_class() } /// Return Ruby's `File` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_file`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_file` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn file() -> RClass { get_ruby!().class_file() } /// Return Ruby's `Float` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_float`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_float` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn float() -> RClass { get_ruby!().class_float() } /// Return Ruby's `Hash` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_hash`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_hash` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn hash() -> RClass { get_ruby!().class_hash() } /// Return Ruby's `IO` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_io`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_io` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn io() -> RClass { get_ruby!().class_io() } /// Return Ruby's `Integer` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_integer`] for /// the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_integer` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn integer() -> RClass { get_ruby!().class_integer() } /// Return Ruby's `MatchData` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_match`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_match` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn match_class() -> RClass { get_ruby!().class_match() } /// Return Ruby's `Method` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_method`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_method` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn method() -> RClass { get_ruby!().class_method() } /// Return Ruby's `Module` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_module`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_module` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn module() -> RClass { get_ruby!().class_module() } /// Return Ruby's `NameError::message` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_name_error_mesg`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_name_error_mesg` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn name_error_mesg() -> RClass { get_ruby!().class_name_error_mesg() } /// Return Ruby's `NilClass` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_nil_class`] for /// the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_nil_class` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn nil_class() -> RClass { get_ruby!().class_nil_class() } /// Return Ruby's `Numeric` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_numeric`] for /// the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_numeric` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn numeric() -> RClass { get_ruby!().class_numeric() } /// Return Ruby's `Object` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_object`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_object` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn object() -> RClass { get_ruby!().class_object() } /// Return Ruby's `Proc` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_proc`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_proc` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn proc() -> RClass { get_ruby!().class_proc() } /// Return Ruby's `Random` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_random`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_random` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn random() -> RClass { get_ruby!().class_random() } /// Return Ruby's `Range` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_range`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_range` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn range() -> RClass { get_ruby!().class_range() } /// Return Ruby's `Rational` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_rational`] for /// the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_rational` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn rational() -> RClass { get_ruby!().class_rational() } /// Return Ruby's `Refinement` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_refinement`] for /// the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_refinement` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[cfg(any(ruby_gte_3_1, docsrs))] #[cfg_attr(docsrs, doc(cfg(ruby_gte_3_1)))] #[inline] pub fn refinement() -> RClass { get_ruby!().class_refinement() } /// Return Ruby's `Regexp` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_regexp`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_regexp` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn regexp() -> RClass { get_ruby!().class_regexp() } /// Return Ruby's `File::Stat` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_stat`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_stat` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn stat() -> RClass { get_ruby!().class_stat() } /// Return Ruby's `String` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_string`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_string` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn string() -> RClass { get_ruby!().class_string() } /// Return Ruby's `Struct` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_struct`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_struct` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn struct_class() -> RClass { get_ruby!().class_struct() } /// Return Ruby's `Symbol` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_symbol`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_symbol` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn symbol() -> RClass { get_ruby!().class_symbol() } /// Return Ruby's `Thread` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_thread`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_thread` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn thread() -> RClass { get_ruby!().class_thread() } /// Return Ruby's `Time` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_time`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_time` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn time() -> RClass { get_ruby!().class_time() } /// Return Ruby's `TrueClass` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_true_class`] for /// the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_true_class` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn true_class() -> RClass { get_ruby!().class_true_class() } /// Return Ruby's `UnboundMethod` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::class_unbound_method`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::class_unbound_method` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn unbound_method() -> RClass { get_ruby!().class_unbound_method() } magnus-0.7.1/src/embed.rs000064400000000000000000000142351046102023000133410ustar 00000000000000//! Helpers for use when embedding Ruby in a Rust project. //! //! See also [`Ruby`](Ruby#embedding) for more embedding related methods. use std::{ ffi::CString, ops::Deref, sync::atomic::{AtomicBool, Ordering}, }; #[cfg(windows)] use rb_sys::rb_w32_sysinit; use rb_sys::{ ruby_cleanup, ruby_exec_node, ruby_init_stack, ruby_process_options, ruby_set_script_name, ruby_setup, VALUE, }; use crate::{ error::{protect, Error}, r_string::IntoRString, value::private::ReprValue, Ruby, }; /// A guard value that will run the cleanup function for the Ruby VM when /// dropped. /// /// This value will [`Deref`] to [`Ruby`]. pub struct Cleanup(Ruby); impl Drop for Cleanup { fn drop(&mut self) { unsafe { ruby_cleanup(0); } } } impl Deref for Cleanup { type Target = Ruby; fn deref(&self) -> &Self::Target { &self.0 } } /// Performs basic initialisation of the Ruby VM. /// /// This only initialises the core of Ruby's functionality, some features may /// be missing or not work as expected. Generally [`init`] should be preferred, /// but there may be some cases where it is not possible to run the full Ruby /// initialisation sequence. /// /// Calling this function is only required when embedding Ruby in Rust. It is /// not required when embedding Rust in Ruby, e.g. in a Ruby Gem. /// /// # Safety /// /// Must be called in `main()`, or at least a function higher up the stack than /// any code calling Ruby. Must not drop Cleanup until the very end of the /// process, after all Ruby execution has finished. Do not use Ruby values /// after Cleanup has been dropped. /// /// # Panics /// /// Panics if this, [`init`], or [`Ruby::init`] are collectively called more /// than once. /// /// # Examples /// /// ``` /// let ruby = unsafe { magnus::embed::setup() }; /// let result: i64 = ruby.eval("2 + 2").unwrap(); /// assert_eq!(result, 4); /// ``` #[inline(always)] pub unsafe fn setup() -> Cleanup { static INIT: AtomicBool = AtomicBool::new(false); let mut variable_in_this_stack_frame: VALUE = 0; ruby_init_stack(&mut variable_in_this_stack_frame as *mut VALUE as *mut _); match INIT.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) { Ok(false) => { #[cfg(windows)] { let mut argc = 0; let mut argv: [*mut std::os::raw::c_char; 0] = []; let mut argv = argv.as_mut_ptr(); rb_w32_sysinit(&mut argc, &mut argv); } if ruby_setup() != 0 { panic!("Failed to setup Ruby"); }; Cleanup(Ruby::get_unchecked()) } Err(true) => panic!("Ruby already initialized"), r => panic!("unexpected INIT state {:?}", r), } } /// Initialises the Ruby VM. /// /// See also [`Ruby::init`] and [`setup`]. /// /// Calling this function is only required when embedding Ruby in Rust. It is /// not required when embedding Rust in Ruby, e.g. in a Ruby Gem. /// /// # Safety /// /// Must be called in `main()`, or at least a function higher up the stack than /// any code calling Ruby. Must not drop Cleanup until the very end of the /// process, after all Ruby execution has finished. Do not use Ruby values /// after Cleanup has been dropped. /// /// # Panics /// /// Panics if this, [`setup`], or [`Ruby::init`] are collectively called more /// than once. /// /// # Examples /// /// ``` /// let ruby = unsafe { magnus::embed::init() }; /// let result: i64 = ruby.eval("2 + 2").unwrap(); /// assert_eq!(result, 4); /// ``` #[inline(always)] pub unsafe fn init() -> Cleanup { let cleanup = setup(); init_options(&["-e", ""]); cleanup } #[inline(always)] unsafe fn init_options(opts: &[&str]) { let mut argv = vec![CString::new("ruby").unwrap()]; argv.extend(opts.iter().map(|s| CString::new(*s).unwrap())); let mut argv = argv .iter() .map(|cs| cs.as_ptr() as *mut _) .collect::>(); let mut node = 0 as _; protect(|| { node = ruby_process_options(argv.len() as i32, argv.as_mut_ptr()); Ruby::get_unchecked().qnil() }) .unwrap(); if ruby_exec_node(node) != 0 { panic!("Ruby init code failed"); }; } /// # Embedding /// /// Functions relevant when embedding Ruby in Rust. /// /// See also the [`embed`](self) module. impl Ruby { /// Initialises the Ruby VM. /// /// See also [`init`] and [`setup`]. /// /// Calling this function is only required when embedding Ruby in Rust. It /// is not required when embedding Rust in Ruby, e.g. in a Ruby Gem. /// /// The Ruby VM can only be initialised once per process, and the Ruby VM /// cleanup will be run once the passed function has completed. /// /// # Safety /// /// This function takes a function pointer, rather than a closure, so that /// it is hard to leak Ruby values that could be used after the Ruby VM has /// finished. It is still possible to leak Ruby values with, for example, /// a `static` with interior mutability. Do not do this. /// /// # Panics /// /// Panics if this, [`init`], or [`setup`] are collectively called more /// than once. /// /// # Examples /// /// ``` /// magnus::Ruby::init(|ruby| { /// let result: i64 = ruby.eval("2 + 2")?; /// assert_eq!(result, 4); /// Ok(()) /// }) /// .unwrap() /// ``` pub fn init(func: fn(&Ruby) -> Result<(), Error>) -> Result<(), String> { func(unsafe { &init() }).map_err(|e| e.to_string()) } /// Sets the current script name. pub fn script(&self, name: T) where T: IntoRString, { let name = name.into_r_string_with(self); unsafe { ruby_set_script_name(name.as_rb_value()) }; } } /// Sets the current script name. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::script`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::script` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn ruby_script(name: T) where T: IntoRString, { get_ruby!().script(name) } magnus-0.7.1/src/encoding.rs000064400000000000000000001252161046102023000140550ustar 00000000000000//! Types and functions for working with encodings. //! //! This module defines 3 types for working with encodings, these types can //! be converted back and forth with [`From`]/[`Into`] like so: //! ``` text //! Encoding <-> RbEncoding <-> Index //! |______________________^ //! ``` //! Many functions that require an encoding take thier arguments as //! `Into` or `Into` to ease working with the different //! types. The type specified for the `Into` conversion hints at the type the //! function nativly works with, and thus will avoid any conversion cost. //! //! [`Encoding`] and [`RbEncoding`] both implement [`TryConvert`] and //! [`IntoValue`] so can be used as parameters and return values in functions //! bound to Ruby. Both convert from either an instance of `Encoding` or a //! string of an encoding name, and convert to an instance of `Encoding`. use std::{ ffi::{CStr, CString}, fmt, ops::Range, os::raw::{c_char, c_int}, ptr::{self, NonNull}, }; use rb_sys::{ rb_ascii8bit_encindex, rb_ascii8bit_encoding, rb_default_external_encoding, rb_default_internal_encoding, rb_enc_ascget, rb_enc_associate_index, rb_enc_check, rb_enc_codelen, rb_enc_codepoint_len, rb_enc_compatible, rb_enc_copy, rb_enc_default_external, rb_enc_default_internal, rb_enc_fast_mbclen, rb_enc_find, rb_enc_find_index, rb_enc_from_encoding, rb_enc_from_index, rb_enc_get_index, rb_enc_mbclen, rb_enc_precise_mbclen, rb_enc_set_index, rb_enc_to_index, rb_enc_uint_chr, rb_encoding, rb_filesystem_encindex, rb_filesystem_encoding, rb_find_encoding, rb_locale_encindex, rb_locale_encoding, rb_to_encoding, rb_to_encoding_index, rb_usascii_encindex, rb_usascii_encoding, rb_utf8_encindex, rb_utf8_encoding, }; use crate::{ error::{protect, Error}, into_value::IntoValue, object::Object, r_string::RString, try_convert::TryConvert, value::{ private::{self, ReprValue as _}, NonZeroValue, ReprValue, Value, }, Ruby, }; /// # `Encoding` /// /// Functions to access pre-defined Encodings. /// /// See also the [`Encoding`] type. impl Ruby { /// Returns the default internal encoding as a Ruby object. /// /// This is the encoding used for anything out-of-process, such as reading /// from files or sockets. pub fn enc_default_external(&self) -> Encoding { Encoding::from_value(Value::new(unsafe { rb_enc_default_external() })).unwrap() } /// Returns the default external encoding as a Ruby object. /// /// If set, any out-of-process data is transcoded from the default external /// encoding to the default internal encoding. pub fn enc_default_internal(&self) -> Option { Encoding::from_value(Value::new(unsafe { rb_enc_default_internal() })) } } /// Wrapper type for a Value known to be an instance of Ruby's Encoding class. /// /// This is the representation of an encoding exposed to Ruby code. /// /// See the [`ReprValue`] and [`Object`] traits for additional methods /// available on this type. See [`Ruby`](Ruby#encoding) for methods to get an /// `Encoding`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct Encoding(NonZeroValue); impl Encoding { /// Return `Some(Encoding)` if `val` is an `Encoding`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{encoding::Encoding, eval}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(Encoding::from_value(eval("Encoding::US_ASCII").unwrap()).is_some()); /// assert!(Encoding::from_value(eval("nil").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { val.is_kind_of(Ruby::get_with(val).class_encoding()) .then(|| Self(NonZeroValue::new_unchecked(val))) } } /// Returns the default internal encoding as a Ruby object. /// /// This is the encoding used for anything out-of-process, such as reading /// from files or sockets. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::enc_default_external`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::enc_default_external` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn default_external() -> Self { get_ruby!().enc_default_external() } /// Returns the default external encoding as a Ruby object. /// /// If set, any out-of-process data is transcoded from the default external /// encoding to the default internal encoding. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::enc_default_internal`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::enc_default_internal` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn default_internal() -> Option { get_ruby!().enc_default_internal() } } impl fmt::Display for Encoding { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for Encoding { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl From for Index { fn from(val: Encoding) -> Self { let i = unsafe { rb_to_encoding_index(val.as_rb_value()) }; if i == -1 { panic!("got encoding index -1"); } Index(i) } } impl From for RbEncoding { fn from(val: Encoding) -> Self { let ptr = unsafe { rb_find_encoding(val.as_rb_value()) }; RbEncoding::new(ptr).expect("got NULL rb_encoding") } } impl IntoValue for Encoding { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl Object for Encoding {} unsafe impl private::ReprValue for Encoding {} impl ReprValue for Encoding {} impl TryConvert for Encoding { fn try_convert(val: Value) -> Result { if let Some(enc) = Self::from_value(val) { return Ok(enc); } RbEncoding::try_convert(val).map(Into::into) } } /// # `RbEncoding` /// /// Functions to access pre-defined encodings. /// /// See also the [`RbEncoding`] type. impl Ruby { /// Returns the encoding that represents ASCII-8BIT a.k.a. binary. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert_eq!(ruby.ascii8bit_encoding().name(), "ASCII-8BIT"); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn ascii8bit_encoding(&self) -> RbEncoding { RbEncoding::new(unsafe { rb_ascii8bit_encoding() }).unwrap() } /// Returns the encoding that represents UTF-8. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert_eq!(ruby.utf8_encoding().name(), "UTF-8"); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn utf8_encoding(&self) -> RbEncoding { RbEncoding::new(unsafe { rb_utf8_encoding() }).unwrap() } /// Returns the encoding that represents US-ASCII. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert_eq!(ruby.usascii_encoding().name(), "US-ASCII"); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn usascii_encoding(&self) -> RbEncoding { RbEncoding::new(unsafe { rb_usascii_encoding() }).unwrap() } /// Returns the encoding that represents the process' current locale. /// /// This is dynamic. If you change the process' locale that should also /// change the return value of this function. pub fn locale_encoding(&self) -> RbEncoding { RbEncoding::new(unsafe { rb_locale_encoding() }).unwrap() } /// Returns the filesystem encoding. /// /// This is the encoding that Ruby expects data from the OS' file system /// to be encoded as, such as directory names. pub fn filesystem_encoding(&self) -> RbEncoding { RbEncoding::new(unsafe { rb_filesystem_encoding() }).unwrap() } /// Returns the default external encoding. /// /// This is the encoding used for anything out-of-process, such as reading /// from files or sockets. pub fn default_external_encoding(&self) -> RbEncoding { RbEncoding::new(unsafe { rb_default_external_encoding() }).unwrap() } /// Returns the default internal encoding. /// /// If set, any out-of-process data is transcoded from the default external /// encoding to the default internal encoding. pub fn default_internal_encoding(&self) -> Option { RbEncoding::new(unsafe { rb_default_internal_encoding() }) } /// Returns the encoding with the name or alias `name`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert_eq!(ruby.find_encoding("BINARY").unwrap().name(), "ASCII-8BIT"); /// assert_eq!(ruby.find_encoding("UTF-8").unwrap().name(), "UTF-8"); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn find_encoding(&self, name: &str) -> Option { let name = CString::new(name).unwrap(); let ptr = unsafe { rb_enc_find(name.as_ptr()) }; RbEncoding::new(ptr) } } /// Ruby's internal encoding type. /// /// This type contains the data for an encoding, and is used with operations /// such as converting a string from one encoding to another, or reading a /// string character by character. /// /// See [`Ruby`](Ruby#rbencoding) for methods to get an `RbEncoding`. #[repr(transparent)] pub struct RbEncoding(NonNull); impl RbEncoding { fn new(inner: *mut rb_encoding) -> Option { NonNull::new(inner).map(Self) } /// Returns the encoding that represents ASCII-8BIT a.k.a. binary. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::ascii8bit_encoding`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::ascii8bit_encoding` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn ascii8bit() -> Self { get_ruby!().ascii8bit_encoding() } /// Returns the encoding that represents UTF-8. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::utf8_encoding`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::utf8_encoding` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn utf8() -> Self { get_ruby!().utf8_encoding() } /// Returns the encoding that represents US-ASCII. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::usascii_encoding`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::usascii_encoding` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn usascii() -> Self { get_ruby!().usascii_encoding() } /// Returns the encoding that represents the process' current locale. /// /// This is dynamic. If you change the process' locale that should also /// change the return value of this function. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::locale_encoding`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::locale_encoding` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn locale() -> Self { get_ruby!().locale_encoding() } /// Returns the filesystem encoding. /// /// This is the encoding that Ruby expects data from the OS' file system /// to be encoded as, such as directory names. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::filesystem_encoding`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::filesystem_encoding` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn filesystem() -> Self { get_ruby!().filesystem_encoding() } /// Returns the default external encoding. /// /// This is the encoding used for anything out-of-process, such as reading /// from files or sockets. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::default_external_encoding`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::default_external_encoding` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn default_external() -> Self { get_ruby!().default_external_encoding() } /// Returns the default internal encoding. /// /// If set, any out-of-process data is transcoded from the default external /// encoding to the default internal encoding. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::default_internal_encoding`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::default_internal_encoding` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn default_internal() -> Option { get_ruby!().default_internal_encoding() } /// Returns the encoding with the name or alias `name`. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::find_encoding`] /// for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::encoding::RbEncoding; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!(RbEncoding::find("UTF-8").unwrap().name(), "UTF-8"); /// assert_eq!(RbEncoding::find("BINARY").unwrap().name(), "ASCII-8BIT"); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::find_encoding` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn find(name: &str) -> Option { get_ruby!().find_encoding(name) } pub(crate) fn as_ptr(&self) -> *mut rb_encoding { self.0.as_ptr() } /// Returns the canonical name of the encoding. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert_eq!(ruby.utf8_encoding().name(), "UTF-8"); /// assert_eq!(ruby.find_encoding("UTF-16").unwrap().name(), "UTF-16"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// # Panics /// /// Panics if the name is not valid UTF-8. Encoding names are expected to /// be ASCII only. pub fn name(&self) -> &str { unsafe { CStr::from_ptr(self.0.as_ref().name).to_str().unwrap() } } /// Returns the minimum number of bytes the encoding needs to represent a /// single character. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert_eq!(ruby.usascii_encoding().mbminlen(), 1); /// assert_eq!(ruby.utf8_encoding().mbminlen(), 1); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn mbminlen(&self) -> usize { unsafe { self.0.as_ref().min_enc_len as usize } } /// Returns the maximum number of bytes the encoding may need to represent /// a single character. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert_eq!(ruby.usascii_encoding().mbmaxlen(), 1); /// assert_eq!(ruby.utf8_encoding().mbmaxlen(), 4); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn mbmaxlen(&self) -> usize { unsafe { self.0.as_ref().max_enc_len as usize } } /// Returns the number of bytes of the first character in `slice`. /// /// If the first byte of `slice` is mid way through a character this will /// return the number of bytes until the next character boundry. /// /// If the slice ends before the last byte of the character this will /// return the number of bytes until the end of the slice. /// /// See also [`fast_mbclen`](RbEncoding::fast_mbclen) and /// [`precise_mbclen`](RbEncoding::precise_mbclen). /// /// # Examples /// /// ``` /// use magnus::{ /// encoding::{EncodingCapable, RbEncoding}, /// Error, Ruby, /// }; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("🦀 café"); /// let encoding: RbEncoding = s.enc_get().into(); /// let mut chars = 0; /// /// unsafe { /// let mut bytes = s.as_slice(); /// assert_eq!(bytes.len(), 10); /// /// while !bytes.is_empty() { /// chars += 1; /// let len = encoding.mbclen(bytes); /// bytes = &bytes[len..]; /// } /// } /// /// assert_eq!(chars, 6); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn mbclen(&self, slice: &[u8]) -> usize { let Range { start: p, end: e } = slice.as_ptr_range(); unsafe { rb_enc_mbclen(p as *const c_char, e as *const c_char, self.as_ptr()) as usize } } /// Returns the number of bytes of the first character in `slice`. /// /// If the first byte of `slice` is mid way through a character this will /// return the number of bytes until the next character boundry. /// /// If the slice ends before the last byte of the character this will /// return the theoretical number of bytes until the end of the character, /// which will be past the end of the slice. If the string has been read /// from an IO source this may indicate more data needs to be read. /// /// See also [`mbclen`](RbEncoding::mbclen) and /// [`precise_mbclen`](RbEncoding::precise_mbclen). /// /// # Examples /// /// ``` /// use magnus::{ /// encoding::{EncodingCapable, RbEncoding}, /// Error, Ruby, /// }; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("🦀 café"); /// let encoding: RbEncoding = s.enc_get().into(); /// let mut chars = 0; /// /// unsafe { /// let mut bytes = s.as_slice(); /// assert_eq!(bytes.len(), 10); /// /// while !bytes.is_empty() { /// chars += 1; /// let len = encoding.fast_mbclen(bytes); /// bytes = &bytes[len..]; /// } /// } /// /// assert_eq!(chars, 6); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn fast_mbclen(&self, slice: &[u8]) -> usize { let Range { start: p, end: e } = slice.as_ptr_range(); unsafe { rb_enc_fast_mbclen(p as *const c_char, e as *const c_char, self.as_ptr()) as usize } } /// Returns the number of bytes of the first character in `slice`. /// /// See also [`mbclen`](RbEncoding::mbclen) and /// [`fast_mbclen`](RbEncoding::fast_mbclen). /// /// # Examples /// /// ``` /// use magnus::{ /// encoding::{EncodingCapable, MbcLen, RbEncoding}, /// Error, Ruby, /// }; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("🦀 café"); /// let encoding: RbEncoding = s.enc_get().into(); /// let mut chars = 0; /// /// unsafe { /// let mut bytes = s.as_slice(); /// assert_eq!(bytes.len(), 10); /// /// while !bytes.is_empty() { /// chars += 1; /// match encoding.precise_mbclen(bytes) { /// MbcLen::CharFound(len) => bytes = &bytes[len..], /// MbcLen::NeedMore(len) => panic!("Met end of string expecting {} bytes", len), /// MbcLen::Invalid => panic!("corrupted string"), /// } /// } /// } /// /// assert_eq!(chars, 6); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn precise_mbclen(&self, slice: &[u8]) -> MbcLen { let Range { start: p, end: e } = slice.as_ptr_range(); let r = unsafe { rb_enc_precise_mbclen(p as *const c_char, e as *const c_char, self.as_ptr()) }; if 0 < r { MbcLen::CharFound(r as usize) } else if r < -1 { MbcLen::NeedMore((-1 - r) as usize) } else if r == -1 { MbcLen::Invalid } else { unreachable!() } } /// If the first character in `slice` is included in ASCII return it and /// its encoded length in `slice`, otherwise returns None. /// /// Typically the length will be 1, but some encodings such as UTF-16 will /// encode ASCII characters in 2 bytes. /// /// # Examples /// /// ``` /// use magnus::{ /// encoding::{EncodingCapable, RbEncoding}, /// Error, Ruby, /// }; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("example"); /// let encoding: RbEncoding = s.enc_get().into(); /// let mut chars = Vec::new(); /// /// unsafe { /// let mut bytes = s.as_slice(); /// /// while !bytes.is_empty() { /// match encoding.ascget(bytes) { /// Some((char, len)) => { /// chars.push(char); /// bytes = &bytes[len..]; /// } /// None => panic!("string not ASCII"), /// } /// } /// } /// /// assert_eq!(chars, [101, 120, 97, 109, 112, 108, 101]); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn ascget(&self, slice: &[u8]) -> Option<(u8, usize)> { let Range { start: p, end: e } = slice.as_ptr_range(); let mut len = 0; let c = unsafe { rb_enc_ascget( p as *const c_char, e as *const c_char, &mut len as *mut _, self.as_ptr(), ) }; if len == 0 { panic!("{:?}", slice); } (c > -1).then(|| (c as u8, len as usize)) } /// Returns the codepoint and length in bytes of the first character in /// `slice`. /// /// # Examples /// /// ``` /// use magnus::{ /// encoding::{EncodingCapable, RbEncoding}, /// Error, Ruby, /// }; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("🦀 café"); /// let encoding: RbEncoding = s.enc_get().into(); /// let mut codepoints = Vec::new(); /// /// unsafe { /// let mut bytes = s.as_slice(); /// /// while !bytes.is_empty() { /// let (codepoint, len) = encoding.codepoint_len(bytes)?; /// codepoints.push(codepoint); /// bytes = &bytes[len..]; /// } /// } /// /// assert_eq!(codepoints, [129408, 32, 99, 97, 102, 233]); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn codepoint_len(&self, slice: &[u8]) -> Result<(u32, usize), Error> { let Range { start: p, end: e } = slice.as_ptr_range(); let mut len = 0; let mut c = 0; protect(|| unsafe { c = rb_enc_codepoint_len( p as *const c_char, e as *const c_char, &mut len as *mut _, self.as_ptr(), ); Ruby::get_unchecked().qnil() })?; Ok((c, len as usize)) } /// Returns the number of bytes required to represent the code point `code` /// in the encoding of `self`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert_eq!(ruby.utf8_encoding().codelen(97)?, 1); /// assert_eq!(ruby.utf8_encoding().codelen(129408)?, 4); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn codelen(&self, code: u32) -> Result { let handle = unsafe { Ruby::get_unchecked() }; let code = code .try_into() .map_err(|e: >::Error| { Error::new(handle.exception_arg_error(), e.to_string()) })?; let mut len = 0; protect(|| { unsafe { len = rb_enc_codelen(code, self.as_ptr()) as usize }; handle.qnil() })?; Ok(len) } /// Encode the codepoint `code` as a series of bytes in the encoding `self` /// and return the result as a Ruby string. /// /// # Examples /// /// ``` /// use magnus::{eval, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let c = ruby.usascii_encoding().chr(97)?; /// let res: bool = eval!(ruby, r#"c == "a""#, c)?; /// assert!(res); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{eval, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let c = ruby.utf8_encoding().chr(129408)?; /// let res: bool = eval!(ruby, r#"c == "🦀""#, c)?; /// assert!(res); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn chr(&self, code: u32) -> Result { protect(|| unsafe { RString::from_rb_value_unchecked(rb_enc_uint_chr(code, self.as_ptr())) }) } /// Returns `true` if the first character in `slice` is a newline in the /// encoding `self`, `false` otherwise. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ruby.utf8_encoding().is_mbc_newline(&[10])); /// assert!(!ruby.utf8_encoding().is_mbc_newline(&[32])); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn is_mbc_newline(&self, slice: &[u8]) -> bool { let Range { start: p, end: e } = slice.as_ptr_range(); unsafe { self.0.as_ref().is_mbc_newline.unwrap()(p as *const _, e as *const _, self.as_ptr()) != 0 } } /// Returns whether the given codepoint `code` is of the character type /// `ctype` in the encoding `self`. /// /// # Examples /// /// ``` /// use magnus::{encoding::CType, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ruby.utf8_encoding().is_code_ctype(9, CType::Space)); // "\t" /// assert!(ruby.utf8_encoding().is_code_ctype(32, CType::Space)); // " " /// assert!(!ruby.utf8_encoding().is_code_ctype(65, CType::Space)); // "A" /// assert!(ruby.utf8_encoding().is_code_ctype(65, CType::Alnum)); // "A" /// assert!(ruby.utf8_encoding().is_code_ctype(65, CType::Upper)); // "A" /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn is_code_ctype(&self, code: u32, ctype: CType) -> bool { unsafe { self.0.as_ref().is_code_ctype.unwrap()(code, ctype as _, self.as_ptr()) != 0 } } } /// Return value for [`RbEncoding::precise_mbclen`]. pub enum MbcLen { /// Found a valid char, value is the char's length. CharFound(usize), /// The slice ended before the end of the current char. Value is the /// theoretical total length of the char. NeedMore(usize), /// The bytes at the start of the slice are not valid for the encoding. Invalid, } /// A character type. #[repr(u32)] #[derive(Debug, Copy, Clone)] pub enum CType { /// Newline. Newline = 0, /// Alphabetical. Alpha = 1, /// Blank. Blank = 2, /// Control. Cntrl = 3, /// Digit. Digit = 4, /// Graph. Graph = 5, /// Lowercase. Lower = 6, /// Printable. Print = 7, /// Punctuation. Punct = 8, /// Whitespace. Space = 9, /// Uppercase. Upper = 10, /// Xdigit. Xdigit = 11, /// Word. Word = 12, /// Alphanumeric. Alnum = 13, /// ASCII. Ascii = 14, } impl From for Encoding { fn from(val: RbEncoding) -> Self { Encoding::from_value(Value::new(unsafe { rb_enc_from_encoding(val.as_ptr()) })).unwrap() } } impl From for Index { fn from(val: RbEncoding) -> Self { Index(unsafe { rb_enc_to_index(val.as_ptr()) }) } } impl IntoValue for RbEncoding { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { Encoding::from(self).into_value_with(handle) } } impl TryConvert for RbEncoding { fn try_convert(val: Value) -> Result { let mut ptr = ptr::null_mut(); protect(|| unsafe { ptr = rb_to_encoding(val.as_rb_value()); Ruby::get_unchecked().qnil() })?; Ok(Self::new(ptr).unwrap()) } } /// # Encoding Index /// /// Functions to access pre-defined encodings. /// /// See also the [`encoding::Index`](Index) type. impl Ruby { /// Returns the index for ASCII-8BIT a.k.a. binary. pub fn ascii8bit_encindex(&self) -> Index { Index(unsafe { rb_ascii8bit_encindex() }) } /// Returns the index for UTF-8. pub fn utf8_encindex(&self) -> Index { Index(unsafe { rb_utf8_encindex() }) } /// Returns the index for US-ASCII. pub fn usascii_encindex(&self) -> Index { Index(unsafe { rb_usascii_encindex() }) } /// Returns the index for the process' current locale encoding. /// /// This is dynamic. If you change the process' locale that should also /// change the return value of this function. pub fn locale_encindex(&self) -> Index { Index(unsafe { rb_locale_encindex() }) } /// Returns the index for filesystem encoding. /// /// This is the encoding that Ruby expects data from the OS' file system /// to be encoded as, such as directory names. pub fn filesystem_encindex(&self) -> Index { Index(unsafe { rb_filesystem_encindex() }) } /// Returns the index for the encoding with the name or alias `name`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ruby.find_encindex("UTF-8").is_ok()); /// assert!(ruby.find_encindex("BINARY").is_ok()); /// assert!(ruby.find_encindex("none").is_err()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn find_encindex(&self, name: &str) -> Result { let name = CString::new(name).unwrap(); let mut i = 0; protect(|| { unsafe { i = rb_enc_find_index(name.as_ptr()) }; self.qnil() })?; if i == -1 { return Err(Error::new( self.exception_runtime_error(), format!("Encoding {:?} exists, but can not be loaded", name), )); } Ok(Index(i)) } } /// The index of an encoding in Ruby's internal encodings table. /// /// This is the type Ruby uses to label encoding capable types, so is used with /// operations that require reading or setting that label. /// /// See [`Ruby`](Ruby#encoding-index) for methods to get an `encoding::Index`. #[derive(Clone, Copy, Eq, PartialEq)] #[repr(transparent)] pub struct Index(c_int); impl Index { /// Returns the index for ASCII-8BIT a.k.a. binary. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::ascii8bit_encindex`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::ascii8bit_encindex` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn ascii8bit() -> Self { get_ruby!().ascii8bit_encindex() } /// Returns the index for UTF-8. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::utf8_encindex`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::utf8_encindex` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn utf8() -> Self { get_ruby!().utf8_encindex() } /// Returns the index for US-ASCII. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::usascii_encindex`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::usascii_encindex` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn usascii() -> Self { get_ruby!().usascii_encindex() } /// Returns the index for the process' current locale encoding. /// /// This is dynamic. If you change the process' locale that should also /// change the return value of this function. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::locale_encindex`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::locale_encindex` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn locale() -> Self { get_ruby!().locale_encindex() } /// Returns the index for filesystem encoding. /// /// This is the encoding that Ruby expects data from the OS' file system /// to be encoded as, such as directory names. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::filesystem_encindex`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::filesystem_encindex` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn filesystem() -> Self { get_ruby!().filesystem_encindex() } /// Returns the index for the encoding with the name or alias `name`. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::find_encindex`] /// for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::encoding; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(encoding::Index::find("UTF-8").is_ok()); /// assert!(encoding::Index::find("BINARY").is_ok()); /// assert!(encoding::Index::find("none").is_err()); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::find_encindex` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn find(name: &str) -> Result { get_ruby!().find_encindex(name) } pub(crate) fn to_int(self) -> c_int { self.0 } } impl From for RbEncoding { fn from(val: Index) -> Self { RbEncoding::new(unsafe { rb_enc_from_index(val.to_int()) }).expect("no encoding for index") } } impl TryConvert for Index { fn try_convert(val: Value) -> Result { let i = unsafe { rb_to_encoding_index(val.as_rb_value()) }; if i == -1 && RString::from_value(val).is_some() { return Err(Error::new( Ruby::get_with(val).exception_runtime_error(), format!("ArgumentError: unknown encoding name - {}", val), )); } else if i == -1 { return TryConvert::try_convert(RString::try_convert(val)?.as_value()); } Ok(Index(i)) } } /// Possible states of how a string matches its encoding. #[repr(u32)] #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Coderange { /// It is unknown if the string is valid for its encoding. Unknown = 0, /// The string is entirely within the 0 to 127 ASCII range. SevenBit = 1048576, /// The string is valid for its encoding. Valid = 2097152, /// The string holds values that are invalid for its encoding. Broken = 3145728, } /// Trait that marks Ruby types cable of having an encoding. pub trait EncodingCapable: ReprValue + Copy { /// Get the encoding of `self`. /// /// # Examples /// /// ``` /// use magnus::{encoding::EncodingCapable, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ruby.str_new("example").enc_get() == ruby.utf8_encindex()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn enc_get(self) -> Index { let i = unsafe { rb_enc_get_index(self.as_rb_value()) }; if i == -1 { panic!("{} not encoding capable", self.as_value()); } Index(i) } /// Set `self`'s encoding. /// /// Returns `Err` if `self` is frozen or the encoding can not be loaded. /// /// See also [`EncodingCapable::enc_associate`]. /// /// # Examples /// /// ``` /// use magnus::{encoding::EncodingCapable, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("example"); /// assert!(s.enc_get() == ruby.utf8_encindex()); /// s.enc_set(ruby.usascii_encindex())?; /// assert!(s.enc_get() == ruby.usascii_encindex()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn enc_set(self, enc: T) -> Result<(), Error> where T: Into, { protect(|| unsafe { rb_enc_set_index(self.as_rb_value(), enc.into().to_int()); Ruby::get_unchecked().qnil() })?; Ok(()) } /// Set `self`'s encoding, along with performing additional fix-up `self`'s /// contents. /// /// For example, Ruby's strings contain an additional terminating null byte /// hidden from Ruby, but allowing for easy c string interop. This method /// will adjust the length of that terminating char depending on the /// encoding. /// /// Returns `Err` if `self` is frozen or the encoding can not be loaded. /// /// # Examples /// /// ``` /// use magnus::{encoding::EncodingCapable, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("example"); /// assert!(s.enc_get() == ruby.utf8_encindex()); /// s.enc_associate(ruby.usascii_encindex())?; /// assert!(s.enc_get() == ruby.usascii_encindex()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn enc_associate(self, enc: T) -> Result<(), Error> where T: Into, { protect(|| { Value::new(unsafe { rb_enc_associate_index(self.as_rb_value(), enc.into().to_int()) }) })?; Ok(()) } } /// Returns the common encoding between `v1` and `v2`, or `None`. /// /// Returns `None` if there is no common compatible encoding. /// /// See also [`check`]. /// /// # Examples /// /// ``` /// use magnus::{encoding, prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = ruby.str_new("a"); /// let b = ruby.str_new("b"); /// /// assert!(a.enc_get() == ruby.utf8_encindex()); /// b.enc_set(ruby.usascii_encindex())?; /// /// assert_eq!(encoding::compatible(a, b).unwrap().name(), "UTF-8"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn compatible(v1: T, v2: U) -> Option where T: EncodingCapable, U: EncodingCapable, { RbEncoding::new(unsafe { rb_enc_compatible(v1.as_rb_value(), v2.as_rb_value()) }) } /// Returns the common encoding between `v1` and `v2`, or `Err`. /// /// Returns `Err` if there is no common compatible encoding. /// /// See also [`compatible`]. /// /// # Examples /// /// ``` /// use magnus::{encoding, prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = ruby.str_new("a"); /// let b = ruby.str_new("b"); /// /// assert!(a.enc_get() == ruby.utf8_encindex()); /// b.enc_set(ruby.usascii_encindex())?; /// /// assert_eq!(encoding::check(a, b)?.name(), "UTF-8"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn check(v1: T, v2: U) -> Result where T: EncodingCapable, U: EncodingCapable, { let mut ptr = ptr::null_mut(); protect(|| unsafe { ptr = rb_enc_check(v1.as_rb_value(), v2.as_rb_value()); Ruby::get_with(v1).qnil() })?; Ok(RbEncoding::new(ptr).unwrap()) } /// Compies the encoding from `src` to `dst`. /// /// This does not reconcode `dst.` /// /// Similar to [`EncodingCapable::enc_associate`], except takes the encoding of /// `src` rather than an encoding object or index. /// /// # Examples /// /// ``` /// use magnus::{encoding, prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = ruby.str_new("a"); /// assert!(a.enc_get() == ruby.utf8_encindex()); /// let b = ruby.str_new("b"); /// assert!(b.enc_get() == ruby.utf8_encindex()); /// /// a.enc_set(ruby.usascii_encindex())?; /// encoding::copy(b, a)?; /// /// assert!(b.enc_get() == ruby.usascii_encindex()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn copy(dst: T, src: U) -> Result<(), Error> where T: EncodingCapable, U: EncodingCapable, { protect(|| unsafe { rb_enc_copy(dst.as_rb_value(), src.as_rb_value()); Ruby::get_with(dst).qnil() })?; Ok(()) } magnus-0.7.1/src/enumerator.rs000064400000000000000000000065561046102023000144550ustar 00000000000000use std::fmt; use rb_sys::VALUE; use crate::{ error::Error, into_value::IntoValue, object::Object, try_convert::TryConvert, value::{ private::{self, ReprValue as _}, NonZeroValue, ReprValue, Value, }, Ruby, }; /// Wrapper type for a Value known to be an instance of Ruby's Enumerator class. /// /// `Enumerator` implements [`Iterator`], however Rust's iterators are a pull /// based model, whereas Ruby's enumerators are a push based model. Bridging /// these two models incurs a performance penalty, so `Enumerator` may not be /// the most performant way of iterating a collection. /// /// See the [`ReprValue`] and [`Object`] traits for additional methods /// available on this type. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("foo\nbar\nbaz"); /// let results = ruby.ary_new(); /// /// // `enumeratorize` returns `Enumerator` /// for line in s.enumeratorize("each_line", ()) { /// results.push(line?)?; /// } /// rb_assert!(r#"results == ["foo\n", "bar\n", "baz"]"#, results); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[derive(Clone, Copy)] #[repr(transparent)] pub struct Enumerator(NonZeroValue); impl Enumerator { /// Return `Some(Enumerator)` if `val` is an `Enumerator`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, Enumerator}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(Enumerator::from_value(eval("[1, 2, 3].each").unwrap()).is_some()); /// assert!(Enumerator::from_value(eval("[1, 2, 3]").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { val.is_kind_of(Ruby::get_with(val).class_enumerator()) .then(|| Self(NonZeroValue::new_unchecked(val))) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } } impl Iterator for Enumerator { type Item = Result; fn next(&mut self) -> Option { match self.funcall("next", ()) { Ok(v) => Some(Ok(v)), Err(e) if e.is_kind_of(Ruby::get_with(*self).exception_stop_iteration()) => None, Err(e) => Some(Err(e)), } } } impl fmt::Display for Enumerator { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for Enumerator { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", ReprValue::inspect(*self)) } } impl IntoValue for Enumerator { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl Object for Enumerator {} unsafe impl private::ReprValue for Enumerator {} impl ReprValue for Enumerator {} impl TryConvert for Enumerator { fn try_convert(val: Value) -> Result { Self::from_value(val).ok_or_else(|| { Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into Enumerator", unsafe { val.classname() },), ) }) } } magnus-0.7.1/src/error.rs000064400000000000000000000442231046102023000134160ustar 00000000000000//! Rust types for working with Ruby Exceptions and other interrupts. //! //! See also [`Ruby`](Ruby#errors) for more error related methods. use std::{any::Any, borrow::Cow, ffi::CString, fmt, mem::transmute, os::raw::c_int}; use rb_sys::{ rb_bug, rb_ensure, rb_errinfo, rb_exc_raise, rb_iter_break_value, rb_jump_tag, rb_protect, rb_set_errinfo, rb_warning, ruby_special_consts, VALUE, }; use crate::{ class::Class, exception::Exception, into_value::IntoValue, module::Module, value::{private::ReprValue as _, ReprValue, Value}, ExceptionClass, Ruby, }; /// An error returned to indicate an attempt to interact with the Ruby API from /// a non-Ruby thread or without aquiring the GVL. #[derive(Debug)] pub enum RubyUnavailableError { /// GVL is not locked. GvlUnlocked, /// Current thread is not a Ruby thread. NonRubyThread, } impl fmt::Display for RubyUnavailableError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::NonRubyThread => write!(f, "Current thread is not a Ruby thread."), Self::GvlUnlocked => write!(f, "GVL is not locked."), } } } impl std::error::Error for RubyUnavailableError {} /// # Errors /// /// Functions for working with errors and flow control encoded as an [`Error`]. /// /// See also [`Error`] and the [`error`](self) module. impl Ruby { /// Create a new error that will break from a loop when returned to Ruby. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let i: i64 = /// ruby.range_new(1, 100, false)? /// .block_call("each", (), |ruby, args, _block| { /// let i = i64::try_convert(*args.get(0).unwrap())?; /// if i % 3 == 0 && i % 5 == 0 { /// Err(ruby.iter_break_value(i)) /// } else { /// Ok(()) /// } /// })?; /// /// assert_eq!(i, 15); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn iter_break_value(&self, val: T) -> Error where T: IntoValue, { let val = self.into_value(val); protect(|| { unsafe { rb_iter_break_value(val.as_rb_value()) }; // we never get here, but this is needed to satisfy the type system #[allow(unreachable_code)] self.qnil() }) .unwrap_err() } /// Outputs `s` to Ruby's stderr if Ruby is configured to output warnings. pub fn warning(&self, s: &str) { let s = CString::new(s).unwrap(); unsafe { rb_warning(s.as_ptr()) }; } } /// Shorthand for `std::result::Result`. pub type Result = std::result::Result; /// The possible types of [`Error`]. #[derive(Debug, Clone)] pub enum ErrorType { /// An interrupt, such as `break` or `throw`. Jump(Tag), /// An error generated in Rust code that will raise an exception when /// returned to Ruby. Error(ExceptionClass, Cow<'static, str>), /// A Ruby `Exception` captured from Ruby as an Error. Exception(Exception), } /// Wrapper type for Ruby `Exception`s or other interrupts. #[derive(Debug, Clone)] pub struct Error(ErrorType); impl Error { /// Create a new `Error` that can be raised as a Ruby `Exception` with /// `msg`. /// /// # Examples /// /// ``` /// use magnus::{function, prelude::*, Error, Exception, Ruby}; /// /// fn bang(ruby: &Ruby) -> Result<(), Error> { /// Err(Error::new(ruby.exception_runtime_error(), "BANG")) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_function("bang", function!(bang, 0)); /// /// let error: Exception = ruby.eval( /// " /// begin /// bang /// rescue => e /// e /// end /// ", /// )?; /// /// assert!(error.is_kind_of(ruby.exception_runtime_error())); /// let msg: String = error.funcall("message", ())?; /// assert_eq!(msg, "BANG"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn new(class: ExceptionClass, msg: T) -> Self where T: Into>, { Self(ErrorType::Error(class, msg.into())) } pub(crate) fn from_tag(tag: Tag) -> Self { Self(ErrorType::Jump(tag)) } /// Create a new error that will break from a loop when returned to Ruby. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::iter_break_value`] /// for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{prelude::*, Error}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let i: i64 = magnus::Range::new(1, 100, false) /// .unwrap() /// .block_call("each", (), |_ruby, args, _block| { /// let i = i64::try_convert(*args.get(0).unwrap())?; /// if i % 3 == 0 && i % 5 == 0 { /// Err(Error::iter_break(i)) /// } else { /// Ok(()) /// } /// }) /// .unwrap(); /// /// assert_eq!(i, 15); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::iter_break_value` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn iter_break(val: T) -> Self where T: IntoValue, { get_ruby!().iter_break_value(val) } /// Matches the internal `Exception` against `class` with same semantics as /// Ruby's `rescue`. /// /// # Examples /// /// ``` /// use magnus::{exception::ExceptionClass, prelude::*, Error, RModule, Ruby, TryConvert, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let err: Error = ruby /// .eval::( /// " /// class ExampleError < StandardError /// end /// module Tag /// end /// class SpecificError < ExampleError /// include Tag /// end /// raise SpecificError /// ", /// ) /// .unwrap_err(); /// /// fn get(ruby: &Ruby, name: &str) -> Result { /// ruby.class_object().const_get::<_, T>(name) /// } /// assert!(err.is_kind_of(get::(ruby, "SpecificError")?)); /// assert!(err.is_kind_of(get::(ruby, "ExampleError")?)); /// assert!(err.is_kind_of(get::(ruby, "StandardError")?)); /// assert!(err.is_kind_of(get::(ruby, "Exception")?)); /// assert!(err.is_kind_of(get::(ruby, "Tag")?)); /// /// assert!(!err.is_kind_of(get::(ruby, "NoMethodError")?)); /// assert!(!err.is_kind_of(get::(ruby, "Math")?)); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn is_kind_of(&self, class: T) -> bool where T: ReprValue + Module, { match self.0 { ErrorType::Jump(_) => false, ErrorType::Error(c, _) => c.is_inherited(class), ErrorType::Exception(e) => e.is_kind_of(class), } } /// Consumes `self`, returning an `Exception`. /// /// # Panics /// /// Panics if called on an `Error::Jump`. fn exception(self) -> Exception { let handle = unsafe { Ruby::get_unchecked() }; match self.0 { ErrorType::Jump(_) => panic!("Error::exception() called on {}", self), ErrorType::Error(class, msg) => { match class.new_instance((handle.str_new(msg.as_ref()),)) { Ok(e) | Err(Error(ErrorType::Exception(e))) => e, Err(err) => unreachable!("*very* unexpected error: {}", err), } } ErrorType::Exception(e) => e, } } /// Returns the [`ErrorType`] for self. pub fn error_type(&self) -> &ErrorType { &self.0 } /// Returns the inner [`Value`] of `self`, if there is one. /// /// The returned `Value` may be a subclass or an instance of `Exception`. /// /// This function is provided for rare cases where the `Error` needs to be /// stored on the heap and the inner value needs to be /// [marked](`crate::gc::Marker::mark`) to avoid being garbage collected. pub fn value(&self) -> Option { match self.0 { ErrorType::Jump(_) => None, ErrorType::Error(c, _) => Some(c.as_value()), ErrorType::Exception(e) => Some(e.as_value()), } } /// Create an `Error` from the error value of [`std::panic::catch_unwind`]. /// /// The Ruby Exception will be `fatal`, terminating the Ruby process, but /// allowing cleanup code to run. pub(crate) fn from_panic(e: Box) -> Self { let msg = if let Some(&m) = e.downcast_ref::<&'static str>() { m.into() } else if let Some(m) = e.downcast_ref::() { m.clone().into() } else { "panic".into() }; Self(ErrorType::Error( unsafe { Ruby::get_unchecked().exception_fatal() }, msg, )) } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.0 { ErrorType::Jump(s) => s.fmt(f), ErrorType::Error(e, m) => write!(f, "{}: {}", e, m), ErrorType::Exception(e) => e.fmt(f), } } } impl From for Error { fn from(val: Exception) -> Self { Self(ErrorType::Exception(val)) } } /// Conversions into [`Error`]. pub trait IntoError { /// Convert `self` into [`Error`]. fn into_error(self, ruby: &Ruby) -> Error; } impl IntoError for Error { #[inline] fn into_error(self, _: &Ruby) -> Error { self } } /// A wrapper to make a [`Error`] [`Send`] + [`Sync`]. /// /// [`Error`] is not [`Send`] or [`Sync`] as it provides a way to call some of /// Ruby's APIs, which are not safe to call from a non-Ruby thread. /// /// [`Error`] is safe to send between Ruby threads, but Rust's trait system /// currently can not model this detail. /// /// To resolve this, the `OpaqueError` type makes an [`Error`] [`Send`] + /// [`Sync`] by removing the ability use it with any Ruby APIs. /// /// [`OpaqueError::into_error_with`] provides a way to safely get an [`Error`] /// from a `OpaqueError`]. /// /// Note that `OpaqueError` contains a Ruby value, so must be kept on the stack /// of a Ruby thread to prevent it from being Garbage Collected (or otherwise /// protected from premature GC). #[derive(Clone)] pub struct OpaqueError(ErrorType); unsafe impl Send for OpaqueError {} unsafe impl Sync for OpaqueError {} impl OpaqueError { /// Convert an `OpaqueError` into an [`Error`]. /// /// # Examples /// /// ``` /// use magnus::{error::OpaqueError, Error, Ruby}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let ruby = Ruby::get().unwrap(); // errors on non-Ruby thread /// let opaque_err = OpaqueError::from(Error::new(ruby.exception_runtime_error(), "test")); /// /// // send to another Ruby thread /// /// let ruby = Ruby::get().unwrap(); // errors on non-Ruby thread /// let err = OpaqueError::into_error_with(opaque_err, &ruby); /// assert!(err.is_kind_of(ruby.exception_runtime_error())); /// ``` #[allow(unused_variables)] pub fn into_error_with(this: Self, handle: &Ruby) -> Error { Error(this.0) } } impl From for OpaqueError { fn from(err: Error) -> Self { Self(err.0) } } impl IntoError for OpaqueError { #[inline] fn into_error(self, _: &Ruby) -> Error { Error(self.0) } } /// The state of a call to Ruby exiting early, interrupting the normal flow /// of code. #[derive(Debug, Clone, Copy)] #[repr(i32)] pub enum Tag { // None = 0, /// Early return from a block. Return = 1, /// Break from a block. Break = 2, /// Early return from a block, continuing to next block call. Next = 3, /// Break from a block after an error, block will be subsequently re-run. Retry = 4, /// Break from a block that will be subsequently re-run. Redo = 5, /// Ruby stack unwound with an error. Raise = 6, /// Ruby stack unwound as flow control. Throw = 7, /// Block or method exiting early due to unrecoverable error. Fatal = 8, } impl Tag { fn resume(self) -> ! { unsafe { rb_jump_tag(self as c_int) }; } } impl fmt::Display for Tag { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { // Self::None => write!(f, "None"), Self::Return => write!(f, "Return"), Self::Break => write!(f, "Break"), Self::Next => write!(f, "Next"), Self::Retry => write!(f, "Retry"), Self::Redo => write!(f, "Redo"), Self::Raise => write!(f, "Raise"), Self::Throw => write!(f, "Throw"), Self::Fatal => write!(f, "Fatal"), } } } /// Calls the given closure, rescuing Ruby Exceptions and returning them as /// an [`Error`]. /// /// All functions exposed by magnus that call Ruby in a way that may raise /// already use this internally, but this is provided for anyone calling /// the Ruby C API directly. pub(crate) fn protect(func: F) -> Result where F: FnOnce() -> T, T: ReprValue, { // nested function as this is totally unsafe to call out of this context // arg should not be a VALUE, but a mutable pointer to F, cast to VALUE unsafe extern "C" fn call(arg: VALUE) -> VALUE where F: FnOnce() -> T, T: ReprValue, { let closure = (*(arg as *mut Option)).take().unwrap(); (closure)().as_rb_value() } // Tag::None let mut state = 0; // rb_protect takes: // arg1: function pointer that returns a VALUE // arg2: a VALUE // arg3: a pointer to an int. // rb_protect then calls arg1 with arg2 and returns the VALUE that arg1 // returns. If a Ruby exception is raised (or other interrupt) the VALUE // returned is instead Qnil, and arg3 is set to non-zero. // As arg2 is only ever passed to arg1 and otherwise not touched we can // pack in whatever data we want that will fit into a VALUE. This is part // of the api and safe to do. // In this case we use arg2 to pass a pointer the Rust closure we actually // want to call, and arg1 is just a simple adapter to call arg2. let result = unsafe { let mut some_func = Some(func); let closure = &mut some_func as *mut Option as VALUE; rb_protect(Some(call::), closure, &mut state as *mut c_int) }; match state { // Tag::None 0 => unsafe { Ok(T::from_value_unchecked(Value::new(result))) }, // Tag::Raise 6 => unsafe { let ex = Exception::from_rb_value_unchecked(rb_errinfo()); rb_set_errinfo(Ruby::get_unchecked().qnil().as_rb_value()); Err(ex.into()) }, other => Err(Error::from_tag(unsafe { transmute(other) })), } } pub(crate) fn ensure(func: F1, ensure: F2) -> T where F1: FnOnce() -> T, F2: FnOnce(), T: ReprValue, { unsafe extern "C" fn call_func(arg: VALUE) -> VALUE where F1: FnOnce() -> T, T: ReprValue, { let closure = (*(arg as *mut Option)).take().unwrap(); (closure)().as_rb_value() } unsafe extern "C" fn call_ensure(arg: VALUE) -> VALUE where F2: FnOnce(), { let closure = (*(arg as *mut Option)).take().unwrap(); (closure)(); ruby_special_consts::RUBY_Qnil as VALUE } let result = unsafe { let call_func_ptr = call_func:: as unsafe extern "C" fn(VALUE) -> VALUE; let mut some_func = Some(func); let func_closure = &mut some_func as *mut Option as VALUE; let call_ensure_ptr = call_ensure:: as unsafe extern "C" fn(VALUE) -> VALUE; let mut some_ensure = Some(ensure); let ensure_closure = &mut some_ensure as *mut Option as VALUE; rb_ensure( Some(call_func_ptr), func_closure, Some(call_ensure_ptr), ensure_closure, ) }; unsafe { T::from_value_unchecked(Value::new(result)) } } pub(crate) fn raise(e: Error) -> ! { match e.0 { ErrorType::Jump(tag) => tag.resume(), _ => { unsafe { rb_exc_raise(e.exception().as_rb_value()) } // friendly reminder: we really never get here, and as such won't // drop any values still in scope, make sure everything has been // consumed/dropped } }; } pub(crate) fn bug_from_panic(e: Box, or: &str) -> ! { let msg: Cow<'_, str> = if let Some(&m) = e.downcast_ref::<&'static str>() { m.into() } else if let Some(m) = e.downcast_ref::() { m.clone().into() } else { or.into() }; bug(&msg) } /// Immediately terminate the process, printing Ruby internal state for /// debugging. pub fn bug(s: &str) -> ! { let s = CString::new(s).unwrap_or_else(|_| CString::new("panic").unwrap()); unsafe { rb_bug(s.as_ptr()) }; // as we never get here `s` isn't dropped, technically this is a memory // leak, in practice we don't care because we just hard crashed } /// Outputs `s` to Ruby's stderr if Ruby is configured to output warnings. /// /// Otherwise does nothing. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::warning`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::warning` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn warning(s: &str) { get_ruby!().warning(s) } magnus-0.7.1/src/exception.rs000064400000000000000000001312651046102023000142660ustar 00000000000000//! Types and functions for working with Ruby exceptions. //! //! See also [`Ruby`](Ruby#core-exceptions) for more exception related methods. use std::fmt; #[cfg(ruby_gte_3_1)] use rb_sys::rb_eNoMatchingPatternKeyError; use rb_sys::{ rb_eArgError, rb_eEOFError, rb_eEncCompatError, rb_eEncodingError, rb_eException, rb_eFatal, rb_eFloatDomainError, rb_eFrozenError, rb_eIOError, rb_eIndexError, rb_eInterrupt, rb_eKeyError, rb_eLoadError, rb_eLocalJumpError, rb_eMathDomainError, rb_eNameError, rb_eNoMatchingPatternError, rb_eNoMemError, rb_eNoMethodError, rb_eNotImpError, rb_eRangeError, rb_eRegexpError, rb_eRuntimeError, rb_eScriptError, rb_eSecurityError, rb_eSignal, rb_eStandardError, rb_eStopIteration, rb_eSyntaxError, rb_eSysStackError, rb_eSystemCallError, rb_eSystemExit, rb_eThreadError, rb_eTypeError, rb_eZeroDivError, VALUE, }; use crate::{ class::{Class, RClass}, error::Error, into_value::{ArgList, IntoValue}, module::Module, object::Object, r_array::RArray, try_convert::TryConvert, value::{ private::{self, ReprValue as _}, NonZeroValue, ReprValue, Value, }, Ruby, }; /// Wrapper type for a Value known to be an instance of Ruby's Exception class. /// /// See the [`ReprValue`] and [`Object`] traits for additional methods /// available on this type. #[derive(Clone, Copy)] #[repr(transparent)] pub struct Exception(NonZeroValue); impl Exception { /// Return `Some(Exception)` if `val` is an `Exception`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, Exception}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(Exception::from_value(eval(r#"StandardError.new("example")"#).unwrap()).is_some()); /// assert!(Exception::from_value(eval("Object.new").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { debug_assert_value!(val); unsafe { val.class() .is_inherited(RClass::from_rb_value_unchecked(rb_eException)) .then(|| Self(NonZeroValue::new_unchecked(val))) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } /// Returns the class that `self` is an instance of, as an /// [`ExceptionClass`]. /// /// See also [`ReprValue::class`]. /// /// # Examples /// /// ``` /// use magnus::{eval, prelude::*, Exception}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let e: Exception = eval(r#"StandardError.new("example")"#).unwrap(); /// let ec = e.exception_class(); /// // safe as we immediately create an owned String and drop the reference /// assert_eq!(unsafe { ec.name().to_owned() }, "StandardError"); /// ``` pub fn exception_class(self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(self.class().as_rb_value()) } } } impl fmt::Display for Exception { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for Exception { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if f.alternate() { unsafe { writeln!(f, "{}: {}", self.classname(), self)?; if let Ok(Some(backtrace)) = self.funcall::<_, _, Option>("backtrace", ()) { for line in backtrace { writeln!(f, "{}", line)?; } } } Ok(()) } else { write!(f, "{}", self.inspect()) } } } impl IntoValue for Exception { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } unsafe impl private::ReprValue for Exception {} impl ReprValue for Exception {} impl TryConvert for Exception { fn try_convert(val: Value) -> Result { if let Some(e) = Self::from_value(val) { return Ok(e); } if let Some(Ok(val)) = val.check_funcall::<_, _, Value>("exception", ()) { if let Some(e) = Self::from_value(val) { return Ok(e); } } Err(Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into Exception", unsafe { val.classname() },), )) } } /// A Value known to be an instance of Class and subclass of Exception. /// /// See the [`ReprValue`] and [`Object`] traits for additional methods /// available on this type. #[derive(Clone, Copy)] #[repr(transparent)] pub struct ExceptionClass(NonZeroValue); impl ExceptionClass { /// Return `Some(ExceptionClass)` if `val` is an `ExceptionClass`, `None` /// otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, ExceptionClass}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(ExceptionClass::from_value(eval("StandardError").unwrap()).is_some()); /// assert!(ExceptionClass::from_value(eval(r#"StandardError.new("example")"#).unwrap()).is_none()); /// assert!(ExceptionClass::from_value(eval("Object").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { debug_assert_value!(val); unsafe { RClass::from_value(val).and_then(|class| { class .is_inherited(RClass::from_rb_value_unchecked(rb_eException)) .then(|| Self(NonZeroValue::new_unchecked(val))) }) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } } impl fmt::Display for ExceptionClass { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for ExceptionClass { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for ExceptionClass { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl Object for ExceptionClass {} impl Module for ExceptionClass {} impl Class for ExceptionClass { type Instance = Exception; fn new(superclass: Self) -> Result { RClass::new(superclass.as_r_class()) .map(|class| unsafe { ExceptionClass::from_value_unchecked(class.as_value()) }) } fn new_instance(self, args: T) -> Result where T: ArgList, { self.as_r_class() .new_instance(args) .map(|ins| unsafe { Exception::from_value_unchecked(ins) }) } fn obj_alloc(self) -> Result { self.as_r_class() .obj_alloc() .map(|ins| unsafe { Exception::from_value_unchecked(ins) }) } fn as_r_class(self) -> RClass { unsafe { RClass::from_value_unchecked(self.as_value()) } } } unsafe impl private::ReprValue for ExceptionClass {} impl ReprValue for ExceptionClass {} impl TryConvert for ExceptionClass { fn try_convert(val: Value) -> Result { Self::from_value(val).ok_or_else(|| { Error::new( Ruby::get_with(val).exception_type_error(), format!( "no implicit conversion of {} into Class inheriting Exception", unsafe { val.classname() }, ), ) }) } } /// # Core Exceptions /// /// Functions to access Ruby's built-in exception classes. /// /// See also the [`exception`](self) module. impl Ruby { /// Return Ruby's `ArgumentError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == ArgumentError", /// klass = ruby.exception_arg_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_arg_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eArgError) } } /// Return Ruby's `EOFError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == EOFError", /// klass = ruby.exception_eof_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_eof_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eEOFError) } } /// Return Ruby's `Encoding::CompatibilityError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == Encoding::CompatibilityError", /// klass = ruby.exception_enc_compat_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_enc_compat_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eEncCompatError) } } /// Return Ruby's `EncodingError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == EncodingError", /// klass = ruby.exception_encoding_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_encoding_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eEncodingError) } } /// Return Ruby's `Exception` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == Exception", /// klass = ruby.exception_exception() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_exception(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eException) } } /// Return Ruby's `fatal` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// r#"klass.name == "fatal""#, /// klass = ruby.exception_fatal() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_fatal(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eFatal) } } /// Return Ruby's `FloatDomainError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == FloatDomainError", /// klass = ruby.exception_float_domain_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_float_domain_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eFloatDomainError) } } /// Return Ruby's `FrozenError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == FrozenError", /// klass = ruby.exception_frozen_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_frozen_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eFrozenError) } } /// Return Ruby's `IOError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "klass == IOError", klass = ruby.exception_io_error()); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_io_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eIOError) } } /// Return Ruby's `IndexError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == IndexError", /// klass = ruby.exception_index_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_index_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eIndexError) } } /// Return Ruby's `Interrupt` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == Interrupt", /// klass = ruby.exception_interrupt() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_interrupt(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eInterrupt) } } /// Return Ruby's `KeyError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == KeyError", /// klass = ruby.exception_key_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_key_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eKeyError) } } /// Return Ruby's `LoadError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == LoadError", /// klass = ruby.exception_load_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_load_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eLoadError) } } /// Return Ruby's `LocalJumpError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == LocalJumpError", /// klass = ruby.exception_local_jump_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_local_jump_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eLocalJumpError) } } /// Return Ruby's `Math::DomainError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == Math::DomainError", /// klass = ruby.exception_math_domain_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_math_domain_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eMathDomainError) } } /// Return Ruby's `NameError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == NameError", /// klass = ruby.exception_name_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_name_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eNameError) } } /// Return Ruby's `NoMatchingPatternError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == NoMatchingPatternError", /// klass = ruby.exception_no_matching_pattern_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_no_matching_pattern_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eNoMatchingPatternError) } } /// Return Ruby's `NoMatchingPatternKeyError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == NoMatchingPatternKeyError", /// klass = ruby.exception_no_matching_pattern_key_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[cfg(any(ruby_gte_3_1, docsrs))] #[cfg_attr(docsrs, doc(cfg(ruby_gte_3_1)))] #[inline] pub fn exception_no_matching_pattern_key_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eNoMatchingPatternKeyError) } } /// Return Ruby's `NoMemoryError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == NoMemoryError", /// klass = ruby.exception_no_mem_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_no_mem_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eNoMemError) } } /// Return Ruby's `NoMethodError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == NoMethodError", /// klass = ruby.exception_no_method_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_no_method_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eNoMethodError) } } /// Return Ruby's `NotImplementedError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == NotImplementedError", /// klass = ruby.exception_not_imp_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_not_imp_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eNotImpError) } } /// Return Ruby's `RangeError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == RangeError", /// klass = ruby.exception_range_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_range_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eRangeError) } } /// Return Ruby's `RegexpError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == RegexpError", /// klass = ruby.exception_regexp_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_regexp_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eRegexpError) } } /// Return Ruby's `RuntimeError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == RuntimeError", /// klass = ruby.exception_runtime_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_runtime_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eRuntimeError) } } /// Return Ruby's `ScriptError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == ScriptError", /// klass = ruby.exception_script_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_script_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eScriptError) } } /// Return Ruby's `SecurityError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == SecurityError", /// klass = ruby.exception_security_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_security_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eSecurityError) } } /// Return Ruby's `SignalException` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == SignalException", /// klass = ruby.exception_signal() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_signal(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eSignal) } } /// Return Ruby's `StandardError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == StandardError", /// klass = ruby.exception_standard_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_standard_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eStandardError) } } /// Return Ruby's `StopIteration` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == StopIteration", /// klass = ruby.exception_stop_iteration() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_stop_iteration(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eStopIteration) } } /// Return Ruby's `SyntaxError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == SyntaxError", /// klass = ruby.exception_syntax_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_syntax_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eSyntaxError) } } /// Return Ruby's `SystemStackError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == SystemStackError", /// klass = ruby.exception_sys_stack_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_sys_stack_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eSysStackError) } } /// Return Ruby's `SystemCallError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == SystemCallError", /// klass = ruby.exception_system_call_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_system_call_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eSystemCallError) } } /// Return Ruby's `SystemExit` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == SystemExit", /// klass = ruby.exception_system_exit() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_system_exit(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eSystemExit) } } /// Return Ruby's `ThreadError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == ThreadError", /// klass = ruby.exception_thread_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_thread_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eThreadError) } } /// Return Ruby's `TypeError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == TypeError", /// klass = ruby.exception_type_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_type_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eTypeError) } } /// Return Ruby's `ZeroDivisionError` class. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "klass == ZeroDivisionError", /// klass = ruby.exception_zero_div_error() /// ); /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn exception_zero_div_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eZeroDivError) } } } /// Return Ruby's `ArgumentError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::exception_arg_error`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_arg_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn arg_error() -> ExceptionClass { get_ruby!().exception_arg_error() } /// Return Ruby's `EOFError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::exception_eof_error`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_eof_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn eof_error() -> ExceptionClass { get_ruby!().exception_eof_error() } /// Return Ruby's `Encoding::CompatibilityError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_enc_compat_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_enc_compat_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn enc_compat_error() -> ExceptionClass { get_ruby!().exception_enc_compat_error() } /// Return Ruby's `EncodingError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_encoding_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_encoding_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn encoding_error() -> ExceptionClass { get_ruby!().exception_encoding_error() } /// Return Ruby's `Exception` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::exception_exception`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_exception` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn exception() -> ExceptionClass { get_ruby!().exception_exception() } /// Return Ruby's `fatal` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::exception_fatal`] for /// the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_fatal` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn fatal() -> ExceptionClass { get_ruby!().exception_fatal() } /// Return Ruby's `FloatDomainError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_float_domain_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_float_domain_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn float_domain_error() -> ExceptionClass { get_ruby!().exception_float_domain_error() } /// Return Ruby's `FrozenError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_frozen_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_frozen_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn frozen_error() -> ExceptionClass { get_ruby!().exception_frozen_error() } /// Return Ruby's `IOError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::exception_io_error`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_io_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn io_error() -> ExceptionClass { get_ruby!().exception_io_error() } /// Return Ruby's `IndexError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_index_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_index_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn index_error() -> ExceptionClass { get_ruby!().exception_index_error() } /// Return Ruby's `Interrupt` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::exception_interrupt`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_interrupt` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn interrupt() -> ExceptionClass { get_ruby!().exception_interrupt() } /// Return Ruby's `KeyError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::exception_key_error`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_key_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn key_error() -> ExceptionClass { get_ruby!().exception_key_error() } /// Return Ruby's `LoadError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::exception_load_error`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_load_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn load_error() -> ExceptionClass { get_ruby!().exception_load_error() } /// Return Ruby's `LocalJumpError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_local_jump_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_local_jump_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn local_jump_error() -> ExceptionClass { get_ruby!().exception_local_jump_error() } /// Return Ruby's `Math::DomainError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_math_domain_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_math_domain_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn math_domain_error() -> ExceptionClass { get_ruby!().exception_math_domain_error() } /// Return Ruby's `NameError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_name_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_name_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn name_error() -> ExceptionClass { get_ruby!().exception_name_error() } /// Return Ruby's `NoMatchingPatternError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_no_matching_pattern_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_no_matching_pattern_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn no_matching_pattern_error() -> ExceptionClass { get_ruby!().exception_no_matching_pattern_error() } /// Return Ruby's `NoMatchingPatternKeyError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_no_matching_pattern_key_error`] for the non-panicking /// version. #[cfg(any(ruby_gte_3_1, docsrs))] #[cfg_attr(docsrs, doc(cfg(ruby_gte_3_1)))] #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_no_matching_pattern_key_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn no_matching_pattern_key_error() -> ExceptionClass { get_ruby!().exception_no_matching_pattern_key_error() } /// Return Ruby's `NoMemoryError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_no_mem_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_no_mem_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn no_mem_error() -> ExceptionClass { get_ruby!().exception_no_mem_error() } /// Return Ruby's `NoMethodError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_no_method_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_no_method_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn no_method_error() -> ExceptionClass { get_ruby!().exception_no_method_error() } /// Return Ruby's `NotImplementedError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_not_imp_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_not_imp_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn not_imp_error() -> ExceptionClass { get_ruby!().exception_not_imp_error() } /// Return Ruby's `RangeError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_range_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_range_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn range_error() -> ExceptionClass { get_ruby!().exception_range_error() } /// Return Ruby's `RegexpError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_regexp_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_regexp_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn regexp_error() -> ExceptionClass { get_ruby!().exception_regexp_error() } /// Return Ruby's `RuntimeError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_runtime_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_runtime_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn runtime_error() -> ExceptionClass { get_ruby!().exception_runtime_error() } /// Return Ruby's `ScriptError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_script_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_script_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn script_error() -> ExceptionClass { get_ruby!().exception_script_error() } /// Return Ruby's `SecurityError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_security_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_security_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn security_error() -> ExceptionClass { get_ruby!().exception_security_error() } /// Return Ruby's `SignalException` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_signal`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_signal` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn signal() -> ExceptionClass { get_ruby!().exception_signal() } /// Return Ruby's `StandardError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_standard_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_standard_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn standard_error() -> ExceptionClass { get_ruby!().exception_standard_error() } /// Return Ruby's `StopIteration` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_stop_iteration`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_stop_iteration` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn stop_iteration() -> ExceptionClass { get_ruby!().exception_stop_iteration() } /// Return Ruby's `SyntaxError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_syntax_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_syntax_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn syntax_error() -> ExceptionClass { get_ruby!().exception_syntax_error() } /// Return Ruby's `SystemStackError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_sys_stack_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_sys_stack_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn sys_stack_error() -> ExceptionClass { get_ruby!().exception_sys_stack_error() } /// Return Ruby's `SystemCallError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_system_call_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_system_call_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn system_call_error() -> ExceptionClass { get_ruby!().exception_system_call_error() } /// Return Ruby's `SystemExit` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_system_exit`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_system_exit` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn system_exit() -> ExceptionClass { get_ruby!().exception_system_exit() } /// Return Ruby's `ThreadError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_thread_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_thread_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn thread_error() -> ExceptionClass { get_ruby!().exception_thread_error() } /// Return Ruby's `TypeError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::exception_type_error`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_type_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn type_error() -> ExceptionClass { get_ruby!().exception_type_error() } /// Return Ruby's `ZeroDivisionError` class. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_zero_div_error`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::exception_zero_div_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn zero_div_error() -> ExceptionClass { get_ruby!().exception_zero_div_error() } magnus-0.7.1/src/fiber.rs000064400000000000000000000444711046102023000133610ustar 00000000000000//! Types and functions for working with Ruby's Fiber class. use std::{fmt, mem::size_of, os::raw::c_int, slice}; #[cfg(ruby_lt_3_2)] use rb_sys::rb_fiber_new; #[cfg(ruby_gte_3_2)] use rb_sys::rb_fiber_new_storage; use rb_sys::{ rb_data_typed_object_wrap, rb_fiber_alive_p, rb_fiber_current, rb_fiber_resume_kw, rb_fiber_yield_kw, VALUE, }; #[cfg(ruby_gte_3_1)] use rb_sys::{rb_fiber_raise, rb_fiber_transfer_kw, rb_obj_is_fiber}; #[cfg(any(ruby_gte_3_2, docsrs))] use crate::r_hash::RHash; use crate::{ api::Ruby, block::Proc, data_type_builder, error::{protect, Error}, exception::Exception, gc, into_value::{kw_splat, ArgList, IntoValue}, method::{Block, BlockReturn}, object::Object, r_typed_data::RTypedData, try_convert::TryConvert, typed_data::{DataType, DataTypeFunctions}, value::{ private::{self, ReprValue as _}, ReprValue, Value, QUNDEF, }, }; /// # `Fiber` /// /// Functions to create and work with Ruby `Fiber`s. /// /// See also the [`Fiber`] type. impl Ruby { /// Create a Ruby Fiber. /// /// As `func` is a function pointer, only functions and closures that do /// not capture any variables are permitted. For more flexibility (at the /// cost of allocating) see [`fiber_new_from_fn`](Ruby::fiber_new_from_fn). /// /// # Examples /// /// ``` /// use magnus::{prelude::*, rb_assert, Error, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let fib = ruby.fiber_new(Default::default(), |ruby, args, _block| { /// let mut a = u64::try_convert(*args.get(0).unwrap())?; /// let mut b = u64::try_convert(*args.get(1).unwrap())?; /// while let Some(c) = a.checked_add(b) { /// let _: Value = ruby.fiber_yield((c,))?; /// a = b; /// b = c; /// } /// Ok(()) /// })?; /// /// rb_assert!(ruby, "fib.resume(0, 1) == 1", fib); /// rb_assert!(ruby, "fib.resume == 2", fib); /// rb_assert!(ruby, "fib.resume == 3", fib); /// rb_assert!(ruby, "fib.resume == 5", fib); /// rb_assert!(ruby, "fib.resume == 8", fib); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn fiber_new( &self, storage: Storage, func: fn(&Ruby, &[Value], Option) -> R, ) -> Result where R: BlockReturn, { unsafe extern "C" fn call( _yielded_arg: VALUE, callback_arg: VALUE, argc: c_int, argv: *const VALUE, blockarg: VALUE, ) -> VALUE where R: BlockReturn, { let func = std::mem::transmute::) -> R>(callback_arg); func.call_handle_error(argc, argv as *const Value, Value::new(blockarg)) .as_rb_value() } let call_func = call:: as unsafe extern "C" fn(VALUE, VALUE, c_int, *const VALUE, VALUE) -> VALUE; unsafe { protect(|| { #[cfg(ruby_gte_3_2)] let value = rb_fiber_new_storage(Some(call_func), func as VALUE, storage.as_rb_value()); #[cfg(ruby_lt_3_2)] let value = rb_fiber_new(Some(call_func), func as VALUE); Fiber::from_rb_value_unchecked(value) }) } } /// Create a Ruby Fiber. /// /// See also [`fiber_new`](Ruby::fiber_new), which is more efficient when /// `func` is a function or closure that does not capture any variables. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let mut a = 0_u64; /// let mut b = 1_u64; /// /// let fib = ruby.fiber_new_from_fn(Default::default(), move |ruby, _args, _block| { /// while let Some(c) = a.checked_add(b) { /// let _: Value = ruby.fiber_yield((c,))?; /// a = b; /// b = c; /// } /// Ok(()) /// })?; /// /// rb_assert!(ruby, "fib.resume == 1", fib); /// rb_assert!(ruby, "fib.resume == 2", fib); /// rb_assert!(ruby, "fib.resume == 3", fib); /// rb_assert!(ruby, "fib.resume == 5", fib); /// rb_assert!(ruby, "fib.resume == 8", fib); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn fiber_new_from_fn(&self, storage: Storage, func: F) -> Result where F: 'static + Send + FnOnce(&Ruby, &[Value], Option) -> R, R: BlockReturn, { unsafe extern "C" fn call( _yielded_arg: VALUE, callback_arg: VALUE, argc: c_int, argv: *const VALUE, blockarg: VALUE, ) -> VALUE where F: FnOnce(&Ruby, &[Value], Option) -> R, R: BlockReturn, { let closure = (*(callback_arg as *mut Option)).take().unwrap(); closure .call_handle_error(argc, argv as *const Value, Value::new(blockarg)) .as_rb_value() } let (closure, keepalive) = wrap_closure(func); let call_func = call:: as unsafe extern "C" fn(VALUE, VALUE, c_int, *const VALUE, VALUE) -> VALUE; protect(|| { #[cfg(ruby_gte_3_2)] let fiber = unsafe { Fiber::from_rb_value_unchecked(rb_fiber_new_storage( Some(call_func), closure as VALUE, storage.as_rb_value(), )) }; #[cfg(ruby_lt_3_2)] let fiber = unsafe { Fiber::from_rb_value_unchecked(rb_fiber_new(Some(call_func), closure as VALUE)) }; // ivar without @ prefix is invisible from Ruby fiber.ivar_set("__rust_closure", keepalive).unwrap(); fiber }) } /// Return the currently executing Fiber. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let fiber = ruby.fiber_current(); /// /// rb_assert!(ruby, "fiber.is_a?(Fiber)", fiber); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn fiber_current(&self) -> Fiber { unsafe { Fiber::from_rb_value_unchecked(rb_fiber_current()) } } /// Transfer execution back to where the current Fiber was resumed. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, value::Opaque, Error, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_new(); /// let send_array = Opaque::from(ary); /// /// let fiber = ruby.fiber_new_from_fn(Default::default(), move |ruby, _args, _block| { /// let ary = ruby.get_inner(send_array); /// ary.push(1)?; /// let _: Value = ruby.fiber_yield(())?; /// ary.push(2)?; /// let _: Value = ruby.fiber_yield(())?; /// ary.push(3)?; /// let _: Value = ruby.fiber_yield(())?; /// Ok(()) /// })?; /// /// ary.push("a")?; /// let _: Value = fiber.resume(())?; /// ary.push("b")?; /// let _: Value = fiber.resume(())?; /// ary.push("c")?; /// let _: Value = fiber.resume(())?; /// /// rb_assert!(ruby, r#"ary == ["a", 1, "b", 2, "c", 3]"#, ary); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn fiber_yield(&self, args: A) -> Result where A: ArgList, T: TryConvert, { let kw_splat = kw_splat(&args); let args = args.into_arg_list_with(self); let slice = args.as_ref(); unsafe { protect(|| { Value::new(rb_fiber_yield_kw( slice.len() as c_int, slice.as_ptr() as *const VALUE, kw_splat as c_int, )) }) .and_then(TryConvert::try_convert) } } } /// Wrapper type for a Value known to be an instance of Ruby's Fiber class. /// /// See the [`ReprValue`] and [`Object`] traits for additional methods /// available on this type. See [`Ruby`](Ruby#fiber) for methods to create a /// `Fiber` and [`Ruby::fiber_yield`]. #[derive(Clone, Copy)] #[repr(transparent)] pub struct Fiber(RTypedData); impl Fiber { /// Return `Some(Fiber)` if `val` is a `Fiber`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::eval; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(magnus::Fiber::from_value(eval("Fiber.new {1 + 2}").unwrap()).is_some()); /// assert!(magnus::Fiber::from_value(eval("Thread.new {1 + 2}").unwrap()).is_none()); /// assert!(magnus::Fiber::from_value(eval("Proc.new {1 + 2}").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { Value::new(rb_obj_is_fiber(val.as_rb_value())) .to_bool() .then(|| Self::from_rb_value_unchecked(val.as_rb_value())) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(RTypedData::from_rb_value_unchecked(val)) } /// Return `true` if `self` can be resumed, `false` otherwise. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let fiber = ruby.fiber_new(Default::default(), move |ruby, _args, _block| { /// let _: Value = ruby.fiber_yield((1,))?; /// let _: Value = ruby.fiber_yield((3,))?; /// Ok(5) /// })?; /// /// assert!(fiber.is_alive()); /// assert_eq!(fiber.resume::<_, u64>(())?, 1); /// assert!(fiber.is_alive()); /// assert_eq!(fiber.resume::<_, u64>(())?, 3); /// assert!(fiber.is_alive()); /// assert_eq!(fiber.resume::<_, u64>(())?, 5); /// assert!(!fiber.is_alive()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn is_alive(self) -> bool { unsafe { Value::new(rb_fiber_alive_p(self.as_rb_value())).to_bool() } } /// Resume the execution of `self`. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let fib = ruby.fiber_new(Default::default(), |ruby, args, _block| { /// let mut a = u64::try_convert(*args.get(0).unwrap())?; /// let mut b = u64::try_convert(*args.get(1).unwrap())?; /// while let Some(c) = a.checked_add(b) { /// let _: Value = ruby.fiber_yield((c,))?; /// a = b; /// b = c; /// } /// Ok(()) /// })?; /// /// assert_eq!(fib.resume::<_, u64>((0, 1))?, 1); /// assert_eq!(fib.resume::<_, u64>(())?, 2); /// assert_eq!(fib.resume::<_, u64>(())?, 3); /// assert_eq!(fib.resume::<_, u64>(())?, 5); /// assert_eq!(fib.resume::<_, u64>(())?, 8); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn resume(self, args: A) -> Result where A: ArgList, T: TryConvert, { let kw_splat = kw_splat(&args); let args = args.into_arg_list_with(&Ruby::get_with(self)); let slice = args.as_ref(); unsafe { protect(|| { Value::new(rb_fiber_resume_kw( self.as_rb_value(), slice.len() as c_int, slice.as_ptr() as *const VALUE, kw_splat as c_int, )) }) .and_then(TryConvert::try_convert) } } /// Transfer control to another Fiber. /// /// `transfer` is an alternate API to /// [`resume`](Fiber::resume)/[`yield`](Ruby::fiber_yield). The two APIs /// can not be mixed, a Fiber muse use *either* `transfer` or /// `resume`/`yield` to pass control. /// /// # Examples /// /// ``` /// use magnus::{Error, Fiber, Ruby, TryConvert, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let fib = ruby.fiber_new(Default::default(), |_ruby, args, _block| { /// let root = Fiber::try_convert(*args.get(0).unwrap())?; /// let mut a = u64::try_convert(*args.get(1).unwrap())?; /// let mut b = u64::try_convert(*args.get(2).unwrap())?; /// while let Some(c) = a.checked_add(b) { /// let _: Value = root.transfer((c,))?; /// a = b; /// b = c; /// } /// Ok(()) /// })?; /// /// assert_eq!(fib.transfer::<_, u64>((ruby.fiber_current(), 0, 1))?, 1); /// assert_eq!(fib.transfer::<_, u64>(())?, 2); /// assert_eq!(fib.transfer::<_, u64>(())?, 3); /// assert_eq!(fib.transfer::<_, u64>(())?, 5); /// assert_eq!(fib.transfer::<_, u64>(())?, 8); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn transfer(self, args: A) -> Result where A: ArgList, T: TryConvert, { let kw_splat = kw_splat(&args); let args = args.into_arg_list_with(&Ruby::get_with(self)); let slice = args.as_ref(); unsafe { protect(|| { Value::new(rb_fiber_transfer_kw( self.as_rb_value(), slice.len() as c_int, slice.as_ptr() as *const VALUE, kw_splat as c_int, )) }) .and_then(TryConvert::try_convert) } } /// Resume the execution of `self`, raising the exception `e`. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let fiber = ruby.fiber_new(Default::default(), move |ruby, _args, _block| { /// assert!(ruby.fiber_yield::<_, Value>(()).is_err()); /// })?; /// /// let _: Value = fiber.resume(())?; /// let _: Value = fiber.raise(ruby.exception_runtime_error().new_instance(("oh no!",))?)?; /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn raise(self, e: Exception) -> Result where T: TryConvert, { unsafe { protect(|| { Value::new(rb_fiber_raise( self.as_rb_value(), 1, &e.as_rb_value() as *const VALUE, )) }) .and_then(TryConvert::try_convert) } } } impl fmt::Display for Fiber { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for Fiber { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for Fiber { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.as_value() } } impl Object for Fiber {} unsafe impl private::ReprValue for Fiber {} impl ReprValue for Fiber {} impl TryConvert for Fiber { fn try_convert(val: Value) -> Result { Self::from_value(val).ok_or_else(|| { Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into Fiber", unsafe { val.classname() },), ) }) } } /// Options for initialising Fiber-local storage. pub enum Storage { /// Inherit the storage from the current Fiber. Inherit, /// Initialise a new empty storage when needed. #[cfg(any(ruby_gte_3_2, docsrs))] #[cfg_attr(docsrs, doc(cfg(ruby_gte_3_2)))] Lazy, /// Use the given Hash as the Fiber's storage. #[cfg(any(ruby_gte_3_2, docsrs))] #[cfg_attr(docsrs, doc(cfg(ruby_gte_3_2)))] Use(RHash), } #[cfg(ruby_gte_3_2)] impl Storage { unsafe fn as_rb_value(&self) -> VALUE { #[cfg(ruby_gte_3_2)] let ruby = Ruby::get_unchecked(); match self { Self::Inherit => QUNDEF.as_value().as_rb_value(), #[cfg(ruby_gte_3_2)] Self::Lazy => ruby.qnil().as_rb_value(), #[cfg(ruby_gte_3_2)] Self::Use(hash) => hash.as_rb_value(), } } } impl Default for Storage { fn default() -> Self { Self::Inherit } } fn wrap_closure(func: F) -> (*mut Option, Value) where F: FnOnce(&Ruby, &[Value], Option) -> R, R: BlockReturn, { struct Closure(Option, DataType); unsafe impl Send for Closure {} impl DataTypeFunctions for Closure { fn mark(&self, marker: &gc::Marker) { // Attempt to mark any Ruby values captured in a closure. // Rust's closures are structs that contain all the values they // have captured. This reads that struct as a slice of VALUEs and // calls rb_gc_mark_locations which calls gc_mark_maybe which // marks VALUEs and ignores non-VALUEs marker.mark_slice(unsafe { slice::from_raw_parts( &self.0 as *const _ as *const Value, size_of::() / size_of::(), ) }); } } let data_type = data_type_builder!(Closure, "rust closure") .free_immediately() .mark() .build(); let boxed = Box::new(Closure(Some(func), data_type)); let ptr = Box::into_raw(boxed); let value = unsafe { Value::new(rb_data_typed_object_wrap( 0, // using 0 for the class will hide the object from ObjectSpace ptr as *mut _, (*ptr).1.as_rb_data_type() as *const _, )) }; unsafe { (&mut (*ptr).0 as *mut Option, value) } } magnus-0.7.1/src/float.rs000064400000000000000000000154651046102023000134000ustar 00000000000000use std::fmt; use rb_sys::{ rb_float_new_in_heap, rb_float_value, rb_flt_rationalize, rb_flt_rationalize_with_prec, rb_to_float, ruby_value_type, VALUE, }; #[cfg(ruby_use_flonum)] use crate::value::Flonum; use crate::{ error::{protect, Error}, into_value::IntoValue, numeric::Numeric, r_rational::RRational, try_convert::TryConvert, value::{ private::{self, ReprValue as _}, NonZeroValue, ReprValue, Value, }, Ruby, }; /// # `Float` /// /// Functions that can be used to create instances of [`Float`]. /// /// See also the [`Float`] type. impl Ruby { /// Create a new `Float` from an `f64`. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let f = ruby.float_from_f64(1.7272337110188893e-77); /// rb_assert!(ruby, "f == 1.7272337110188893e-77", f); /// /// let f = ruby.float_from_f64(1.7272337110188890e-77); /// rb_assert!(ruby, "f == 1.7272337110188890e-77", f); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn float_from_f64(&self, n: f64) -> Float { unsafe { #[cfg(ruby_use_flonum)] let val = Flonum::from_f64_impl(n) .map(|f| f.as_rb_value()) .unwrap_or_else(|| rb_float_new_in_heap(n)); #[cfg(not(ruby_use_flonum))] let val = rb_float_new_in_heap(n); Float::from_rb_value_unchecked(val) } } } /// A type wrapping either a [`Flonum`](crate::value::Flonum) or an /// [`RFloat`](crate::r_float::RFloat) value. /// /// See the [`ReprValue`] trait for additional methods available on this type. /// See [`Ruby`](Ruby#float) for methods to create a `Float`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct Float(NonZeroValue); impl Float { /// Return `Some(Float)` if `val` is a `Float`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, Float}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(Float::from_value(eval("1.7272337110188893e-77").unwrap()).is_some()); /// assert!(Float::from_value(eval("1.7272337110188890e-77").unwrap()).is_some()); /// assert!(Float::from_value(eval("1").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { if cfg!(ruby_use_flonum) && val.is_flonum() { return Some(Self(NonZeroValue::new_unchecked(val))); } debug_assert_value!(val); (val.rb_type() == ruby_value_type::RUBY_T_FLOAT) .then(|| Self(NonZeroValue::new_unchecked(val))) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } /// Create a new `Float` from an `f64`. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::float_from_f64`] /// for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{rb_assert, Float}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let f = Float::from_f64(1.7272337110188893e-77); /// rb_assert!("f == 1.7272337110188893e-77", f); /// /// let f = Float::from_f64(1.7272337110188890e-77); /// rb_assert!("f == 1.7272337110188890e-77", f); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::float_from_f64` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn from_f64(n: f64) -> Self { get_ruby!().float_from_f64(n) } /// Convert `self` to a `f64`. /// /// # Examples /// /// ``` /// use magnus::{eval, Float}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let f: Float = eval("2.0").unwrap(); /// assert_eq!(f.to_f64(), 2.0); /// ``` #[inline] pub fn to_f64(self) -> f64 { #[cfg(ruby_use_flonum)] if let Some(flonum) = Flonum::from_value(self.as_value()) { return flonum.to_f64(); } unsafe { rb_float_value(self.as_rb_value()) } } /// Returns a rational approximation of `self`. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let pi = ruby.float_from_f64(3.141592); /// /// let r = pi.rationalize_with_prec(ruby.float_from_f64(0.001)); /// rb_assert!(ruby, "r == 201/64r", r); /// /// let r = pi.rationalize_with_prec(ruby.float_from_f64(0.01)); /// rb_assert!(ruby, "r == 22/7r", r); /// /// let r = pi.rationalize_with_prec(ruby.float_from_f64(0.1)); /// rb_assert!(ruby, "r == 16/5r", r); /// /// let r = pi.rationalize_with_prec(ruby.float_from_f64(1.)); /// rb_assert!(ruby, "r == 3/1r", r); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn rationalize_with_prec(self, prec: Self) -> RRational { unsafe { RRational::from_rb_value_unchecked(rb_flt_rationalize_with_prec( self.as_rb_value(), prec.as_rb_value(), )) } } /// Returns a rational approximation of `self`. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let pi = ruby.float_from_f64(3.141592); /// rb_assert!(ruby, "r = 392699/125000r", r = pi.rationalize()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn rationalize(self) -> RRational { unsafe { RRational::from_rb_value_unchecked(rb_flt_rationalize(self.as_rb_value())) } } } impl fmt::Display for Float { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for Float { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for Float { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } unsafe impl private::ReprValue for Float {} impl Numeric for Float {} impl ReprValue for Float {} impl TryConvert for Float { fn try_convert(val: Value) -> Result { match Self::from_value(val) { Some(i) => Ok(i), None => protect(|| { debug_assert_value!(val); unsafe { Self::from_rb_value_unchecked(rb_to_float(val.as_rb_value())) } }), } } } magnus-0.7.1/src/gc.rs000064400000000000000000000444461046102023000126650ustar 00000000000000//! Functions for working with Ruby's Garbage Collector. //! //! See also [`Ruby`](Ruby#gc) for more GC related methods. use std::{marker::PhantomData, ops::Range}; use rb_sys::{ rb_gc_adjust_memory_usage, rb_gc_count, rb_gc_disable, rb_gc_enable, rb_gc_location, rb_gc_mark, rb_gc_mark_locations, rb_gc_mark_movable, rb_gc_register_address, rb_gc_register_mark_object, rb_gc_start, rb_gc_stat, rb_gc_unregister_address, VALUE, }; use crate::{ error::{protect, Error}, r_hash::RHash, symbol::IntoSymbol, value::{private::ReprValue as _, ReprValue, Value}, Ruby, }; pub(crate) mod private { use super::*; pub trait Mark { fn raw(self) -> VALUE; } pub trait Locate { fn raw(self) -> VALUE; fn from_raw(val: VALUE) -> Self; } } /// Trait indicating types that can be passed to GC marking functions. /// /// See [`Marker`]. pub trait Mark: private::Mark {} impl private::Mark for T where T: ReprValue, { fn raw(self) -> VALUE { self.as_rb_value() } } impl Mark for T where T: ReprValue {} impl private::Mark for crate::value::Opaque where T: ReprValue, { fn raw(self) -> VALUE { unsafe { Ruby::get_unchecked() } .get_inner(self) .as_rb_value() } } impl Mark for crate::value::Opaque where T: ReprValue {} /// A handle to GC marking functions. /// /// See also /// [`DataTypeFunctions::mark`](`crate::typed_data::DataTypeFunctions::mark`). pub struct Marker(PhantomData<*mut ()>); impl Marker { pub(crate) fn new() -> Self { Self(PhantomData) } /// Mark an Object. /// /// Used to mark any stored Ruby objects when implementing /// [`DataTypeFunctions::mark`](`crate::typed_data::DataTypeFunctions::mark`). pub fn mark(&self, value: T) where T: Mark, { unsafe { rb_gc_mark(value.raw()) }; } /// Mark multiple Objects. /// /// Used to mark any stored Ruby objects when implementing /// [`DataTypeFunctions::mark`](`crate::typed_data::DataTypeFunctions::mark`). pub fn mark_slice(&self, values: &[T]) where T: Mark, { let Range { start, end } = values.as_ptr_range(); unsafe { rb_gc_mark_locations(start as *const VALUE, end as *const VALUE) } } /// Mark an Object and let Ruby know it is moveable. /// /// The [`Value`] type is effectly a pointer to a Ruby object. Ruby's /// garbage collector will avoid moving objects exposed to extensions, /// unless you use this function to mark them during the GC marking phase. /// /// Used to mark any stored Ruby objects when implementing /// [`DataTypeFunctions::mark`](`crate::typed_data::DataTypeFunctions::mark`) /// and you have also implemented /// [`DataTypeFunctions::compact`](`crate::typed_data::DataTypeFunctions::compact`). /// /// Beware that any Ruby object passed to this function may later become /// invalid to use from Rust when GC is run, you must update any stored /// objects with [`Compactor::location`] inside your implementation of /// [`DataTypeFunctions::compact`](`crate::typed_data::DataTypeFunctions::compact`). pub fn mark_movable(&self, value: T) where T: Mark, { unsafe { rb_gc_mark_movable(value.raw()) }; } } /// Trait indicating types that can given to [`Compactor::location`]. pub trait Locate: private::Locate {} impl private::Locate for T where T: ReprValue, { fn raw(self) -> VALUE { self.as_rb_value() } fn from_raw(val: VALUE) -> Self { unsafe { Self::from_value_unchecked(Value::new(val)) } } } impl Locate for T where T: ReprValue {} impl private::Locate for crate::value::Opaque where T: ReprValue, { fn raw(self) -> VALUE { unsafe { Ruby::get_unchecked() } .get_inner(self) .as_rb_value() } fn from_raw(val: VALUE) -> Self { unsafe { T::from_value_unchecked(Value::new(val)) }.into() } } impl Locate for crate::value::Opaque where T: ReprValue {} /// A handle to functions relating to GC compaction. /// /// See also /// [`DataTypeFunctions::compact`](`crate::typed_data::DataTypeFunctions::compact`). pub struct Compactor(PhantomData<*mut ()>); impl Compactor { pub(crate) fn new() -> Self { Self(PhantomData) } /// Get the new location of an object. /// /// The [`Value`] type is effectly a pointer to a Ruby object. Ruby's /// garbage collector will avoid moving objects exposed to extensions, /// unless the object has been marked with /// [`mark_movable`](Marker::mark_movable). When implementing /// [`DataTypeFunctions::compact`](`crate::typed_data::DataTypeFunctions::compact`) /// you will need to update any Ruby objects you are storing. /// /// Returns a new `T` that is pointing to the object that `value` used to /// point to. If `value` hasn't moved, simply returns `value`. pub fn location(&self, value: T) -> T where T: Locate, { unsafe { T::from_raw(rb_gc_location(value.raw())) } } } /// Registers `value` to never be garbage collected. /// /// This is essentially a deliberate memory leak. /// /// # Examples /// /// ``` /// use magnus::{gc, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// // will never be collected /// let root = ruby.ary_new(); /// gc::register_mark_object(root); /// /// // won't be collected while it is in our `root` array /// let s = ruby.str_new("example"); /// root.push(s).unwrap(); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn register_mark_object(value: T) where T: Mark, { unsafe { rb_gc_register_mark_object(value.raw()) } } /// Inform Ruby's garbage collector that `valref` points to a live Ruby object. /// /// Prevents Ruby moving or collecting `valref`. This should be used on /// `static` items to prevent them being collected instead of relying on Ruby /// constants/globals to allways refrence the value. /// /// See also [`BoxValue`](crate::value::BoxValue). /// /// # Examples /// /// ``` /// use magnus::{gc, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("example"); /// /// // s won't be collected even though it's on the heap /// let boxed = Box::new(s); /// gc::register_address(&*boxed); /// /// // ... /// /// // allow s to be collected /// gc::unregister_address(&*boxed); /// drop(boxed); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn register_address(valref: &T) where T: Mark, { unsafe { rb_gc_register_address(valref as *const _ as *mut VALUE) } } /// Inform Ruby's garbage collector that `valref` that was previously /// registered with [`register_address`] no longer points to a live Ruby /// object. /// /// # Examples /// /// ``` /// use magnus::{gc, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("example"); /// /// // s won't be collected even though it's on the heap /// let boxed = Box::new(s); /// gc::register_address(&*boxed); /// /// // ... /// /// // allow s to be collected /// gc::unregister_address(&*boxed); /// drop(boxed); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn unregister_address(valref: &T) where T: Mark, { unsafe { rb_gc_unregister_address(valref as *const _ as *mut VALUE) } } /// # GC /// /// Functions for working with Ruby's Garbage Collector. /// /// See also the [`gc`](self) module. impl Ruby { /// Disable automatic GC runs. /// /// This could result in other Ruby api functions unexpectedly raising /// `NoMemError`. /// /// Returns `true` if GC was already disabled, `false` otherwise. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let was_disabled = ruby.gc_disable(); /// /// // GC is off /// /// // return GC to previous state /// if !was_disabled { /// ruby.gc_enable(); /// } /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn gc_disable(&self) -> bool { unsafe { Value::new(rb_gc_disable()).to_bool() } } /// Enable automatic GC run. /// /// Garbage Collection is enabled by default, calling this function only /// makes sense if [`disable`] was previously called. /// /// Returns `true` if GC was previously disabled, `false` otherwise. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let was_disabled = ruby.gc_enable(); /// /// // GC is on /// /// // return GC to previous state /// if was_disabled { /// ruby.gc_disable(); /// } /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn gc_enable(&self) -> bool { unsafe { Value::new(rb_gc_enable()).to_bool() } } /// Trigger a "full" GC run. /// /// This will perform a full mark phase and a complete sweep phase, but may /// not run every single proceess associated with garbage collection. /// /// Finalisers will be deferred to run later. /// /// Currently (with versions of Ruby that support compaction) it will not /// trigger compaction. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.gc_start(); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn gc_start(&self) { unsafe { rb_gc_start() }; } /// Inform Ruby of external memory usage. /// /// The Ruby GC is run when Ruby thinks it's running out of memory, but /// won't take into account any memory allocated outside of Ruby api /// functions. This function can be used to give Ruby a more accurate idea /// of how much memory the process is using. /// /// Pass negative numbers to indicate memory has been freed. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let buf = Vec::::with_capacity(1024 * 1024); /// let mem_size = buf.capacity() * std::mem::size_of::(); /// ruby.gc_adjust_memory_usage(mem_size as isize); /// /// // ... /// /// drop(buf); /// ruby.gc_adjust_memory_usage(-(mem_size as isize)); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn gc_adjust_memory_usage(&self, diff: isize) { unsafe { rb_gc_adjust_memory_usage(diff as _) }; } /// Returns the number of garbage collections that have been run since the /// start of the process. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let before = ruby.gc_count(); /// ruby.gc_start(); /// assert!(ruby.gc_count() > before); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn gc_count(&self) -> usize { unsafe { rb_gc_count() as usize } } /// Returns the GC profiling value for `key`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ruby.gc_stat("heap_live_slots")? > 1); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn gc_stat(&self, key: T) -> Result where T: IntoSymbol, { let sym = key.into_symbol_with(self); let mut res = 0; protect(|| { res = unsafe { rb_gc_stat(sym.as_rb_value()) as usize }; self.qnil() })?; Ok(res) } /// Returns all possible key/value pairs for [`stat`] as a Ruby Hash. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let stats = ruby.gc_all_stats(); /// let live_slots: usize = stats.fetch(ruby.to_symbol("heap_live_slots"))?; /// assert!(live_slots > 1); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn gc_all_stats(&self) -> RHash { let res = self.hash_new(); unsafe { rb_gc_stat(res.as_rb_value()) }; res } } /// Disable automatic GC runs. /// /// This could result in other Ruby api functions unexpectedly raising /// `NoMemError`. /// /// Returns `true` if GC was already disabled, `false` otherwise. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::gc_disable`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::gc; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let was_disabled = gc::disable(); /// /// // GC is off /// /// // return GC to previous state /// if !was_disabled { /// gc::enable(); /// } /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::gc_disable` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn disable() -> bool { get_ruby!().gc_disable() } /// Enable automatic GC run. /// /// Garbage Collection is enabled by default, calling this function only makes /// sense if [`disable`] was previously called. /// /// Returns `true` if GC was previously disabled, `false` otherwise. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::gc_enable`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::gc; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let was_disabled = gc::enable(); /// /// // GC is on /// /// // return GC to previous state /// if was_disabled { /// gc::disable(); /// } /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::gc_enable` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn enable() -> bool { get_ruby!().gc_enable() } /// Trigger a "full" GC run. /// /// This will perform a full mark phase and a complete sweep phase, but may not /// run every single proceess associated with garbage collection. /// /// Finalisers will be deferred to run later. /// /// Currently (with versions of Ruby that support compaction) it will not /// trigger compaction. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::gc_start`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::gc; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// gc::start(); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::gc_start` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn start() { get_ruby!().gc_start() } /// Inform Ruby of external memory usage. /// /// The Ruby GC is run when Ruby thinks it's running out of memory, but won't /// take into account any memory allocated outside of Ruby api functions. This /// function can be used to give Ruby a more accurate idea of how much memory /// the process is using. /// /// Pass negative numbers to indicate memory has been freed. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::gc_adjust_memory_usage`] for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::gc; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let buf = Vec::::with_capacity(1024 * 1024); /// let mem_size = buf.capacity() * std::mem::size_of::(); /// gc::adjust_memory_usage(mem_size as isize); /// /// // ... /// /// drop(buf); /// gc::adjust_memory_usage(-(mem_size as isize)); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::gc_adjust_memory_usage` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn adjust_memory_usage(diff: isize) { get_ruby!().gc_adjust_memory_usage(diff) } /// Returns the number of garbage collections that have been run since the /// start of the process. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::gc_count`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::gc; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let before = gc::count(); /// gc::start(); /// assert!(gc::count() > before); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::gc_count` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn count() -> usize { get_ruby!().gc_count() } /// Returns the GC profiling value for `key`. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::gc_stat`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::gc; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(gc::stat("heap_live_slots").unwrap() > 1); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::gc_stat` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn stat(key: T) -> Result where T: IntoSymbol, { let handle = get_ruby!(); handle.gc_stat(key.into_symbol_with(&handle)) } /// Returns all possible key/value pairs for [`stat`] as a Ruby Hash. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::gc_all_stats`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{gc, Symbol}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let stats = gc::all_stats(); /// let live_slots: usize = stats.fetch(Symbol::new("heap_live_slots")).unwrap(); /// assert!(live_slots > 1); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::gc_all_stats` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn all_stats() -> RHash { get_ruby!().gc_all_stats() } magnus-0.7.1/src/integer.rs000064400000000000000000000525421046102023000137250ustar 00000000000000use std::{ fmt, ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}, os::raw::c_long, }; use rb_sys::{ rb_big_cmp, rb_big_div, rb_big_eq, rb_big_minus, rb_big_mul, rb_big_norm, rb_big_plus, rb_int2big, rb_ll2inum, rb_to_int, rb_ull2inum, ruby_special_consts, ruby_value_type, Qtrue, VALUE, }; use crate::{ error::{protect, Error}, into_value::IntoValue, numeric::Numeric, r_bignum::RBignum, try_convert::TryConvert, value::{ private::{self, ReprValue as _}, Fixnum, NonZeroValue, ReprValue, Value, }, Ruby, }; pub(crate) enum IntegerType { Fixnum(Fixnum), Bignum(RBignum), } /// # `Integer` /// /// Functions that can be used to create instances of [`Integer`]. /// /// See also the [`Integer`] type. impl Ruby { /// Create a new `Integer` from an `i64.` /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "i == 0", i = ruby.integer_from_i64(0)); /// rb_assert!( /// ruby, /// "i == 4611686018427387904", /// i = ruby.integer_from_i64(4611686018427387904), /// ); /// rb_assert!( /// ruby, /// "i == -4611686018427387905", /// i = ruby.integer_from_i64(-4611686018427387905), /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn integer_from_i64(&self, n: i64) -> Integer { unsafe { Integer::from_rb_value_unchecked( Fixnum::from_i64_impl(n) .map(|f| f.as_rb_value()) .unwrap_or_else(|| rb_ll2inum(n)), ) } } /// Create a new `Integer` from a `u64.` /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!("i == 0", i = ruby.integer_from_u64(0)); /// rb_assert!( /// "i == 4611686018427387904", /// i = ruby.integer_from_u64(4611686018427387904), /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn integer_from_u64(&self, n: u64) -> Integer { unsafe { Integer::from_rb_value_unchecked( Fixnum::from_i64_impl(i64::try_from(n).unwrap_or(i64::MAX)) .map(|f| f.as_rb_value()) .unwrap_or_else(|| rb_ull2inum(n)), ) } } } /// A type wrapping either a [`Fixnum`] or a [`RBignum`]. /// /// See the [`ReprValue`] trait for additional methods available on this type. /// See [`Ruby`](Ruby#integer) for methods to create an `Integer`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct Integer(NonZeroValue); impl Integer { /// Return `Some(Integer)` if `val` is an `Integer`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(Integer::from_value(eval("0").unwrap()).is_some()); /// assert!(Integer::from_value(eval("9223372036854775807").unwrap()).is_some()); /// // not an int /// assert!(Integer::from_value(eval("1.23").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { if val.as_rb_value() & ruby_special_consts::RUBY_FIXNUM_FLAG as VALUE != 0 { return Some(Self(NonZeroValue::new_unchecked(val))); } debug_assert_value!(val); (val.rb_type() == ruby_value_type::RUBY_T_BIGNUM) .then(|| Self(NonZeroValue::new_unchecked(val))) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } pub(crate) fn integer_type(self) -> IntegerType { unsafe { if self.as_rb_value() & ruby_special_consts::RUBY_FIXNUM_FLAG as VALUE != 0 { IntegerType::Fixnum(Fixnum::from_rb_value_unchecked(self.as_rb_value())) } else { IntegerType::Bignum(RBignum::from_rb_value_unchecked(self.as_rb_value())) } } } /// Create a new `Integer` from an `i64.` /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::integer_from_i64`] /// for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{rb_assert, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// rb_assert!("i == 0", i = Integer::from_i64(0)); /// rb_assert!( /// "i == 4611686018427387904", /// i = Integer::from_i64(4611686018427387904), /// ); /// rb_assert!( /// "i == -4611686018427387905", /// i = Integer::from_i64(-4611686018427387905), /// ); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::integer_from_i64` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn from_i64(n: i64) -> Self { get_ruby!().integer_from_i64(n) } /// Create a new `Integer` from a `u64.` /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::integer_from_u64`] /// for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{rb_assert, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// rb_assert!("i == 0", i = Integer::from_u64(0)); /// rb_assert!( /// "i == 4611686018427387904", /// i = Integer::from_u64(4611686018427387904), /// ); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::integer_from_u64` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn from_u64(n: u64) -> Self { get_ruby!().integer_from_u64(n) } /// Convert `self` to an `i8`. Returns `Err` if `self` is out of range for /// `i8`. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!(eval::("127").unwrap().to_i8().unwrap(), 127); /// assert!(eval::("128").unwrap().to_i8().is_err()); /// assert_eq!(eval::("-128").unwrap().to_i8().unwrap(), -128); /// assert!(eval::("-129").unwrap().to_i8().is_err()); /// ``` #[inline] pub fn to_i8(self) -> Result { match self.integer_type() { IntegerType::Fixnum(fix) => fix.to_i8(), IntegerType::Bignum(_) => Err(Error::new( Ruby::get_with(self).exception_range_error(), "bignum too big to convert into `i8`", )), } } /// Convert `self` to an `i16`. Returns `Err` if `self` is out of range for /// `i16`. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!(eval::("32767").unwrap().to_i16().unwrap(), 32767); /// assert!(eval::("32768").unwrap().to_i16().is_err()); /// assert_eq!(eval::("-32768").unwrap().to_i16().unwrap(), -32768); /// assert!(eval::("-32769").unwrap().to_i16().is_err()); /// ``` #[inline] pub fn to_i16(self) -> Result { match self.integer_type() { IntegerType::Fixnum(fix) => fix.to_i16(), IntegerType::Bignum(_) => Err(Error::new( Ruby::get_with(self).exception_range_error(), "bignum too big to convert into `i16`", )), } } /// Convert `self` to an `i32`. Returns `Err` if `self` is out of range for /// `i32`. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!( /// eval::("2147483647").unwrap().to_i32().unwrap(), /// 2147483647 /// ); /// assert!(eval::("2147483648").unwrap().to_i32().is_err()); /// assert_eq!( /// eval::("-2147483648").unwrap().to_i32().unwrap(), /// -2147483648 /// ); /// assert!(eval::("-2147483649").unwrap().to_i32().is_err()); /// ``` #[inline] pub fn to_i32(self) -> Result { match self.integer_type() { IntegerType::Fixnum(fix) => fix.to_i32(), IntegerType::Bignum(big) => big.to_i32(), } } /// Convert `self` to an `i64`. Returns `Err` if `self` is out of range for /// `i64`. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!( /// eval::("4611686018427387903") /// .unwrap() /// .to_i64() /// .unwrap(), /// 4611686018427387903 /// ); /// assert_eq!( /// eval::("-4611686018427387904") /// .unwrap() /// .to_i64() /// .unwrap(), /// -4611686018427387904 /// ); /// assert!(eval::("9223372036854775808") /// .unwrap() /// .to_i64() /// .is_err()); /// assert!(eval::("-9223372036854775809") /// .unwrap() /// .to_i64() /// .is_err()); /// ``` #[inline] pub fn to_i64(self) -> Result { match self.integer_type() { IntegerType::Fixnum(fix) => Ok(fix.to_i64()), IntegerType::Bignum(big) => big.to_i64(), } } /// Convert `self` to an `isize`. Returns `Err` if `self` is out of range /// for `isize`. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!( /// eval::("4611686018427387903") /// .unwrap() /// .to_isize() /// .unwrap(), /// 4611686018427387903 /// ); /// assert_eq!( /// eval::("-4611686018427387904") /// .unwrap() /// .to_isize() /// .unwrap(), /// -4611686018427387904 /// ); /// ``` #[inline] pub fn to_isize(self) -> Result { match self.integer_type() { IntegerType::Fixnum(fix) => Ok(fix.to_isize()), IntegerType::Bignum(big) => big.to_isize(), } } /// Convert `self` to a `u8`. Returns `Err` if `self` is negative or out of /// range for `u8`. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!(eval::("255").unwrap().to_u8().unwrap(), 255); /// assert!(eval::("256").unwrap().to_u8().is_err()); /// assert!(eval::("-1").unwrap().to_u8().is_err()); /// ``` #[inline] pub fn to_u8(self) -> Result { match self.integer_type() { IntegerType::Fixnum(fix) => fix.to_u8(), IntegerType::Bignum(_) => Err(Error::new( Ruby::get_with(self).exception_range_error(), "bignum too big to convert into `u8`", )), } } /// Convert `self` to a `u16`. Returns `Err` if `self` is negative or out /// of range for `u16`. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!(eval::("65535").unwrap().to_u16().unwrap(), 65535); /// assert!(eval::("65536").unwrap().to_u16().is_err()); /// assert!(eval::("-1").unwrap().to_u16().is_err()); /// ``` #[inline] pub fn to_u16(self) -> Result { match self.integer_type() { IntegerType::Fixnum(fix) => fix.to_u16(), IntegerType::Bignum(_) => Err(Error::new( Ruby::get_with(self).exception_range_error(), "bignum too big to convert into `u16`", )), } } /// Convert `self` to a `u32`. Returns `Err` if `self` is negative or out /// of range for `u32`. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!( /// eval::("4294967295").unwrap().to_u32().unwrap(), /// 4294967295 /// ); /// assert!(eval::("4294967296").unwrap().to_u32().is_err()); /// assert!(eval::("-1").unwrap().to_u32().is_err()); /// ``` #[inline] pub fn to_u32(self) -> Result { match self.integer_type() { IntegerType::Fixnum(fix) => fix.to_u32(), IntegerType::Bignum(big) => big.to_u32(), } } /// Convert `self` to a `u64`. Returns `Err` if `self` is negative or out /// of range for `u64`. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!( /// eval::("4611686018427387903") /// .unwrap() /// .to_u64() /// .unwrap(), /// 4611686018427387903 /// ); /// assert!(eval::("-1").unwrap().to_u64().is_err()); /// assert!(eval::("18446744073709551616") /// .unwrap() /// .to_u64() /// .is_err()); /// ``` #[inline] pub fn to_u64(self) -> Result { match self.integer_type() { IntegerType::Fixnum(fix) => fix.to_u64(), IntegerType::Bignum(big) => big.to_u64(), } } /// Convert `self` to a `usize`. Returns `Err` if `self` is negative or out /// of range for `usize`. /// /// # Examples /// /// ``` /// use magnus::{eval, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!( /// eval::("4611686018427387903") /// .unwrap() /// .to_usize() /// .unwrap(), /// 4611686018427387903 /// ); /// assert!(eval::("-1").unwrap().to_usize().is_err()); /// ``` #[inline] pub fn to_usize(self) -> Result { match self.integer_type() { IntegerType::Fixnum(fix) => fix.to_usize(), IntegerType::Bignum(big) => big.to_usize(), } } /// Normalize `self`. If `self` is a `Fixnum`, returns `self`. If `self` is /// a `Bignum`, if it is small enough to fit in a `Fixnum`, returns a /// `Fixnum` with the same value. Otherwise, returns `self`. pub fn norm(&self) -> Self { match self.integer_type() { IntegerType::Fixnum(_) => *self, IntegerType::Bignum(big) => unsafe { Integer::from_rb_value_unchecked(rb_big_norm(big.as_rb_value())) }, } } fn binary_operation_visit( &self, other: &Self, rust_op: fn(Fixnum, Fixnum) -> T, ruby_op: fn(VALUE, VALUE) -> T, ) -> T { match self.integer_type() { IntegerType::Bignum(a) => ruby_op(a.as_rb_value(), other.as_rb_value()), IntegerType::Fixnum(a) => match other.integer_type() { IntegerType::Bignum(b) => { let a = unsafe { rb_int2big(a.to_isize()) }; ruby_op(a, b.as_rb_value()) } IntegerType::Fixnum(b) => rust_op(a, b), }, } } } impl fmt::Display for Integer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for Integer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for Integer { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl Numeric for Integer {} unsafe impl private::ReprValue for Integer {} impl ReprValue for Integer {} impl TryConvert for Integer { fn try_convert(val: Value) -> Result { match Self::from_value(val) { Some(i) => Ok(i), None => protect(|| { debug_assert_value!(val); unsafe { Self::from_rb_value_unchecked(rb_to_int(val.as_rb_value())) } }), } } } impl PartialEq for Integer { fn eq(&self, other: &Self) -> bool { match self.integer_type() { IntegerType::Bignum(a) => unsafe { rb_big_eq(a.as_rb_value(), other.as_rb_value()) == Qtrue.into() }, IntegerType::Fixnum(a) => a.as_rb_value() == other.norm().as_rb_value(), } } } impl PartialOrd for Integer { fn partial_cmp(&self, other: &Self) -> Option { self.binary_operation_visit( other, |a, b| (a.as_rb_value() as c_long).partial_cmp(&(b.as_rb_value() as c_long)), |a, b| unsafe { let result = rb_big_cmp(a, b); Integer::from_rb_value_unchecked(result) .to_i64() .unwrap() .partial_cmp(&0) }, ) } } impl Add for Integer { type Output = Self; fn add(self, other: Self) -> Self { self.binary_operation_visit( &other, |a, b| { let raw_a = a.as_rb_value() as c_long; let raw_b = b.as_rb_value() as c_long; let result = raw_a.checked_add(raw_b).and_then(|i| i.checked_sub(1)); if let Some(result) = result { unsafe { Integer::from_rb_value_unchecked(result as VALUE) } } else { let a = unsafe { rb_int2big(a.to_isize()) }; let result = unsafe { rb_big_plus(a, b.as_rb_value()) }; unsafe { Integer::from_rb_value_unchecked(result) } } }, |a, b| { let result = unsafe { rb_big_plus(a, b) }; unsafe { Integer::from_rb_value_unchecked(result) } }, ) } } impl AddAssign for Integer { fn add_assign(&mut self, other: Self) { *self = *self + other; } } impl Sub for Integer { type Output = Self; fn sub(self, other: Self) -> Self { self.binary_operation_visit( &other, |a, b| { let raw_a = a.as_rb_value() as c_long; let raw_b = b.as_rb_value() as c_long; let result = raw_a.checked_sub(raw_b).and_then(|i| i.checked_add(1)); if let Some(result) = result { unsafe { Integer::from_rb_value_unchecked(result as VALUE) } } else { let a = unsafe { rb_int2big(a.to_isize()) }; let result = unsafe { rb_big_minus(a, b.as_rb_value()) }; unsafe { Integer::from_rb_value_unchecked(result) } } }, |a, b| { let result = unsafe { rb_big_minus(a, b) }; unsafe { Integer::from_rb_value_unchecked(result) } }, ) } } impl SubAssign for Integer { fn sub_assign(&mut self, other: Self) { *self = *self - other; } } impl Mul for Integer { type Output = Self; fn mul(self, other: Self) -> Self { self.binary_operation_visit( &other, |a, b| { let raw_a = a.to_i64(); let raw_b = b.to_i64(); let result = raw_a.checked_mul(raw_b); if let Some(result) = result { Ruby::get_with(a).integer_from_i64(result) } else { let a = unsafe { rb_int2big(a.to_isize()) }; let result = unsafe { rb_big_mul(a, b.as_rb_value()) }; unsafe { Integer::from_rb_value_unchecked(result) } } }, |a, b| { let result = unsafe { rb_big_mul(a, b) }; unsafe { Integer::from_rb_value_unchecked(result) } }, ) } } impl MulAssign for Integer { fn mul_assign(&mut self, other: Self) { *self = *self * other; } } impl Div for Integer { type Output = Self; fn div(self, other: Self) -> Self { self.binary_operation_visit( &other, |a, b| { let raw_a = a.to_i64(); let raw_b = b.to_i64(); // the only case when division can overflow is when dividing // i64::MIN by -1, but Fixnum can't represent that I64::MIN // so we can safely not use checked_div here Ruby::get_with(a).integer_from_i64(raw_a / raw_b) }, |a, b| { let result = unsafe { rb_big_div(a, b) }; unsafe { Integer::from_rb_value_unchecked(result) } }, ) } } impl DivAssign for Integer { fn div_assign(&mut self, other: Self) { *self = *self / other; } } magnus-0.7.1/src/into_value.rs000064400000000000000000000160441046102023000144320ustar 00000000000000use seq_macro::seq; use crate::{ r_array::RArray, r_hash::RHash, value::{ReprValue, Value}, Ruby, }; /// # Conversion to `Value` /// /// Helpers for the [`IntoValue`] trait. /// /// See also the [`IntoValue`] trait. impl Ruby { /// Convert `val` into [`Value`]. #[allow(clippy::wrong_self_convention)] #[inline] pub fn into_value(&self, val: T) -> Value where T: IntoValue, { val.into_value_with(self) } } /// Conversions from Rust types into [`Value`]. pub trait IntoValue: Sized { /// Convert `self` into [`Value`]. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`IntoValue::into_value_with`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `IntoValue::into_value_with` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] fn into_value(self) -> Value { self.into_value_with(&get_ruby!()) } /// Convert `self` into [`Value`]. /// /// # Safety /// /// This method should only be called from a Ruby thread. #[inline] unsafe fn into_value_unchecked(self) -> Value { self.into_value_with(&Ruby::get_unchecked()) } /// Convert `self` into [`Value`]. fn into_value_with(self, handle: &Ruby) -> Value; } /// Conversions from Rust types that do not contain [`Value`] into [`Value`]. /// /// This trait is used as a bound in functions such as /// [`RArray::from_vec`](crate::r_array::RArray::from_vec) to prevent accepting /// heap allocated datastructures containing `Value`, as it is not safe to /// store a `Value` on the heap. /// /// # Safety /// /// This trait must not be implemented for types that contain `Value`. pub unsafe trait IntoValueFromNative: IntoValue {} /// Trait for types that can be used as an arguments list when calling Ruby /// methods. pub trait ArgList { /// The specific Ruby value type. type Value: ReprValue; /// The type of the arguments list. Must convert to `&[Self::Value]` with /// [`AsRef`]. type Output: AsRef<[Self::Value]>; /// Convert `self` into a type that can be used as a Ruby argument list. fn into_arg_list_with(self, handle: &Ruby) -> Self::Output; /// Whether the argument list contains keyword arguments. If true, the /// last element of the `&[Self::Value]` produced by /// `Self::into_arg_list_with` and [`AsRef`] must be a [`RHash`] fn contains_kw_args(&self) -> bool; } pub(crate) fn kw_splat(args: &impl RArrayArgList) -> u32 { if args.contains_kw_args() { rb_sys::RB_PASS_KEYWORDS } else { rb_sys::RB_NO_KEYWORDS } } /// Wrapper for [`RHash`] intended for use in the tuple implementations of /// [`ArgList`] to indicate that the last argument in the tuple is to be /// passed as keyword arguments. #[repr(transparent)] pub struct KwArgs(pub RHash); /// Create a [`KwArgs`] from Rust key-value mappings. Keys must be string /// literals, while values can be anything that implements [`IntoValue`]. /// /// # Panics /// /// Panics if called from a non-Ruby thread. /// /// # Examples /// /// ``` /// use magnus::{eval, kwargs, prelude::*, RObject}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let kwargs = kwargs!("a" => 1, "b" => 2); /// let object: RObject = eval!( /// r#" /// class Adder /// def add(a:, b:) /// a + b /// end /// end /// /// Adder.new /// "# /// ) /// .unwrap(); /// /// let result: i32 = object.funcall("add", (kwargs,)).unwrap(); /// assert_eq!(3, result); /// ``` #[macro_export] macro_rules! kwargs { ($ruby:expr, $($k:literal => $v:expr),+) => {{ use $crate::IntoValue; let h = $ruby.hash_new(); $( h.aset( $ruby.to_symbol($k), $v.into_value_with($ruby), ).unwrap(); )+ $crate::KwArgs(h) }}; ($($k:literal => $v:expr),+) => {{ $crate::kwargs!(&$crate::Ruby::get().unwrap(), $($k => $v),+) }}; } /// # Safety /// /// The implmentation of `ArgList` for slices is not intended to suggest that /// it is valid to build a `Vec` of Ruby values to then convert to a slice. /// [Ruby values should never be put into a `Vec`](crate#safety). impl<'a, T> ArgList for &'a [T] where T: ReprValue, { type Value = T; type Output = &'a [Self::Value]; fn into_arg_list_with(self, _: &Ruby) -> Self::Output { self } fn contains_kw_args(&self) -> bool { false } } macro_rules! impl_arg_list { ($n:literal) => { seq!(N in 0..$n { impl<#(T~N,)*> ArgList for (#(T~N,)*) where #(T~N: IntoValue,)* { type Value = Value; type Output = [Self::Value; $n]; #[allow(unused_variables)] fn into_arg_list_with(self, handle: &Ruby) -> Self::Output { [#(handle.into_value(self.N),)*] } fn contains_kw_args(&self) -> bool { false } } }); } } seq!(N in 0..=12 { impl_arg_list!(N); }); macro_rules! impl_arg_list_kw { ($n:literal) => { seq!(N in 0..$n { impl<#(T~N,)*> ArgList for (#(T~N,)* KwArgs,) where #(T~N: IntoValue,)* { type Value = Value; type Output = [Self::Value; { $n + 1 }]; #[allow(unused_variables)] fn into_arg_list_with(self, handle: &Ruby) -> Self::Output { [#(handle.into_value(self.N),)* handle.into_value(self.$n.0)] } fn contains_kw_args(&self) -> bool { true } } }); } } seq!(N in 0..=12 { impl_arg_list_kw!(N); }); impl ArgList for [T; N] where T: ReprValue, { type Value = T; type Output = [Self::Value; N]; fn into_arg_list_with(self, _: &Ruby) -> Self::Output { self } fn contains_kw_args(&self) -> bool { false } } /// Trait for types that can be used as an arguments list when calling Ruby /// Procs. pub trait RArrayArgList { /// Convert `self` into a type that can be used as a Ruby Proc argument /// list. fn into_array_arg_list_with(self, handle: &Ruby) -> RArray; /// Whether the argument list contains keyword arguments. If true, the /// last element of the `RArray` produced by /// `Self::into_array_arg_list_with` must be a [`RHash`] fn contains_kw_args(&self) -> bool; } impl RArrayArgList for RArray { fn into_array_arg_list_with(self, _: &Ruby) -> RArray { self } fn contains_kw_args(&self) -> bool { false } } impl RArrayArgList for T where T: ArgList, { fn into_array_arg_list_with(self, handle: &Ruby) -> RArray { handle.ary_new_from_values(self.into_arg_list_with(handle).as_ref()) } fn contains_kw_args(&self) -> bool { ::contains_kw_args(self) } } magnus-0.7.1/src/lib.rs000064400000000000000000002501151046102023000130320ustar 00000000000000//! Magnus is a library for writing Ruby extentions in Rust, or running Ruby //! code from Rust. //! //! # Overview //! //! All Ruby objects are represented by [`Value`]. To make it easier to work //! with values that are instances of specific classes a number of wrapper //! types are available. These wrappers and [`Value`] all implement the //! [`ReprValue`] trait, so share many methods. //! //! | Ruby Class | Magnus Type | //! |------------|-------------| //! | `String` | [`RString`] | //! | `Integer` | [`Integer`] | //! | `Float` | [`Float`] | //! | `Array` | [`RArray`] | //! | `Hash` | [`RHash`] | //! | `Symbol` | [`Symbol`] | //! | `Class` | [`RClass`] | //! | `Module` | [`RModule`] | //! //! When writing Rust code to be called from Ruby the [`init`] attribute can //! be used to mark your init function that Ruby will call when your library //! is `require`d. //! //! When embedding Ruby in a Rust program, see [`embed::init`] for initialising //! the Ruby VM. //! //! The [`method`](`macro@method`) macro can be used to wrap a Rust function //! with automatic type conversion and error handing so it can be exposed to //! Ruby. The [`TryConvert`] trait handles conversions from Ruby to Rust, and //! anything implementing [`IntoValue`] can be returned to Ruby. See the //! [`Module`] and [`Object`] traits for defining methods. //! //! [`Value::funcall`] can be used to call Ruby methods from Rust. //! //! See the [`wrap`] attribute macro for wrapping Rust types as Ruby objects. //! //! ## 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. //! //! # Examples //! //! ``` //! use magnus::{function, method, prelude::*, Error, Ruby}; //! //! #[magnus::wrap(class = "Euclid::Point", free_immediately, size)] //! 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(a: &Point, b: &Point) -> f64 { //! (((b.x - a.x).pow(2) + (b.y - a.y).pow(2)) as f64).sqrt() //! } //! //! #[magnus::init] //! fn init(ruby: &Ruby) -> Result<(), Error> { //! let module = ruby.define_module("Euclid")?; //! let class = module.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))?; //! module.define_module_function("distance", function!(distance, 2))?; //! Ok(()) //! } //! # Ruby::init(init).unwrap() //! ``` //! //! # Crates that work with Magnus //! //! * [`rb-sys`](https://docs.rs/rb-sys) - low level bindings to Ruby. //! * [`serde_magnus`](https://docs.rs/serde_magnus) - Serde integration. //! //! # C Function Index //! //! This lists all the Ruby C API functions currently implemented, and how they //! map to Rust functions and methods in Magnus. // // Currently unimplemented listed here, but not included in doc comments. The // plan is to start listing what's planned to implement, what can't/won't be // implemented, alternatives, etc. //!
//! Click to show //! //! ## A-N // * `Check_Type`: //! * `Data_Get_Struct`: See [`wrap`] and [`TypedData`]. //! * `Data_Make_Struct`: See [`wrap`] and [`TypedData`]. //! * `Data_Wrap_Struct`: See [`wrap`] and [`TypedData`]. // * `ExportStringValue`: //! // ## `onig` //! // * `onigenc_ascii_only_case_map`: // * `onigenc_get_default_encoding`: // * `onigenc_get_left_adjust_char_head`: // * `onigenc_get_prev_char_head`: // * `onigenc_get_right_adjust_char_head`: // * `onigenc_get_right_adjust_char_head_with_prev`: // * `onigenc_init`: // * `onigenc_mbclen_approximate`: // * `onigenc_set_default_encoding`: // * `onigenc_step_back`: // * `onigenc_strlen`: // * `onigenc_strlen_null`: // * `onigenc_str_bytelen_null`: // * `onig_capture_tree_traverse`: // * `onig_copyright`: // * `onig_copy_encoding`: // * `onig_copy_syntax`: // * `onig_end`: // * `onig_error_code_to_str`: // * `onig_foreach_name`: // * `onig_free`: // * `onig_free_body`: // * `onig_get_case_fold_flag`: // * `onig_get_default_case_fold_flag`: // * `onig_get_encoding`: // * `onig_get_match_stack_limit_size`: // * `onig_get_options`: // * `onig_get_parse_depth_limit`: // * `onig_get_syntax`: // * `onig_get_syntax_behavior`: // * `onig_get_syntax_op`: // * `onig_get_syntax_op2`: // * `onig_get_syntax_options`: // * `onig_init`: // * `onig_initialize`: // * `onig_match`: // * `onig_name_to_backref_number`: // * `onig_name_to_group_numbers`: // * `onig_new`: // * `onig_new_deluxe`: // * `onig_new_without_alloc`: // * `onig_noname_group_capture_is_active`: // * `onig_null_warn`: // * `onig_number_of_captures`: // * `onig_number_of_capture_histories`: // * `onig_number_of_names`: // * `onig_region_clear`: // * `onig_region_copy`: // * `onig_region_free`: // * `onig_region_init`: // * `onig_region_new`: // * `onig_region_resize`: // * `onig_region_set`: // * `onig_reg_init`: // * `onig_scan`: // * `onig_search`: // * `onig_search_gpos`: // * `onig_set_default_case_fold_flag`: // * `onig_set_default_syntax`: // * `onig_set_match_stack_limit_size`: // * `onig_set_meta_char`: // * `onig_set_parse_depth_limit`: // * `onig_set_syntax_behavior`: // * `onig_set_syntax_op`: // * `onig_set_syntax_op2`: // * `onig_set_syntax_options`: // * `onig_set_verb_warn_func`: // * `onig_set_warn_func`: // * `onig_version`: //! ## `RARRAY` // //! * `RARRAY`: Similar to [`RArray::from_value`]. //! * `RARRAY_ASET`: Similar to [`RArray::store`]. //! * `RARRAY_CONST_PTR`: Similar to [`RArray::as_slice`]. // * `RARRAY_CONST_PTR_TRANSIENT`: //! * `RARRAY_EMBED_LEN`: See [`RArray::len`]. //! * `RARRAY_LEN`: [`RArray::len`]. //! * `RARRAY_LENINT`: [`RArray::len`]. //! * `RARRAY_PTR`: Similar to [`RArray::as_slice`]. // * `RARRAY_PTR_USE`: // * `RARRAY_PTR_USE_END`: // * `RARRAY_PTR_USE_END_TRANSIENT`: // * `RARRAY_PTR_USE_START`: // * `RARRAY_PTR_USE_START_TRANSIENT`: // * `RARRAY_PTR_USE_TRANSIENT`: // * `RARRAY_TRANSIENT_P`: //! //! ## RB // * `RBASIC`: // * `RBASIC_CLASS`: //! * `RBIGNUM_NEGATIVE_P`: See [`RBignum::is_negative`]. //! * `RBIGNUM_POSITIVE_P`: See [`RBignum::is_positive`]. // * `RBIGNUM_SIGN`: //! //! ## `rb_a`-`rb_arx` // * `rb_absint_numwords`: // * `rb_absint_singlebit_p`: // * `rb_absint_size`: // * `rb_add_event_hook`: // * `rb_add_event_hook2`: //! * `rb_alias`: [`Module::define_alias`]. //! * `rb_alias_variable`: [`Ruby::alias_variable`]. // * `RB_ALLOC`: // * `RB_ALLOCV`: // * `RB_ALLOCV_END`: // * `RB_ALLOCV_N`: // * `RB_ALLOC_N`: // * `rb_alloc_tmp_buffer`: // * `rb_alloc_tmp_buffer2`: // * `rb_alloc_tmp_buffer_with_count`: //! * `rb_any_to_s`: [`std::fmt::Display`]. // * `rb_apply`: // * `rb_argv`: // * `rb_arithmetic_sequence_beg_len_step`: // * `rb_arithmetic_sequence_extract`: // * `rb_Array`: // * `rb_array_len`: //! //! ## `rb_ary` // * `rb_ary_aref`: //! * `rb_ary_assoc`: [`RArray::assoc`]. //! * `rb_ary_cat`: [`RArray::cat`]. //! * `rb_ary_clear`: [`RArray::clear`]. //! * `rb_ary_cmp`: [`RArray::cmp`]. //! * `rb_ary_concat`: [`RArray::concat`]. //! * `rb_ary_delete`: [`RArray::delete`]. //! * `rb_ary_delete_at`: [`RArray::delete_at`]. // * `rb_ary_detransient`: //! * `rb_ary_dup`: Similar to [`RArray::dup`]. //! * `rb_ary_each`: See [`RArray::each`]. //! * `rb_ary_entry`: [`RArray::entry`]. // * `rb_ary_free`: //! * `rb_ary_freeze`: See [`Value::freeze`]. //! * `rb_ary_includes`: [`RArray::includes`]. // * `rb_ary_join`: [`RArray::join`]. // * `rb_ary_modify`: //! * `rb_ary_new`: [`RArray::new`]. //! * `rb_ary_new_capa`: [`RArray::with_capacity`]. //! * `rb_ary_new_from_args`: Not implemented, see [`RArray::from_slice`]. //! * `rb_ary_new_from_values`: [`RArray::from_slice`]. //! * `rb_ary_plus`: [`RArray::plus`]. //! * `rb_ary_pop`: [`RArray::pop`]. // * `rb_ary_ptr_use_end`: // * `rb_ary_ptr_use_start`: //! * `rb_ary_push`: [`RArray::push`]. //! * `rb_ary_rassoc`: [`RArray::rassoc`]. //! * `rb_ary_replace`: [`RArray::replace`]. //! * `rb_ary_resize`: [`RArray::resize`]. // * `rb_ary_resurrect`: //! * `rb_ary_reverse`: [`RArray::reverse`]. //! * `rb_ary_rotate`: [`RArray::rotate`]. //! * `rb_ary_shared_with_p`: [`RArray::is_shared`]. //! * `rb_ary_shift`: [`RArray::shift`]. //! * `rb_ary_sort`: Not implemented, see [`RArray::sort`]. //! * `rb_ary_sort_bang`: [`RArray::sort`]. //! * `rb_ary_store`: [`RArray::store`]. //! * `rb_ary_subseq`: [`RArray::subseq`]. // * `rb_ary_tmp_new`: //! * `rb_ary_to_ary`: [`RArray::to_ary`]. //! * `rb_ary_to_s`: Not implemented directly, see [`Value::to_r_string`]. //! * `rb_ary_unshift`: [`RArray::unshift`]. //! //! ## `rb_as`-`rb_az` //! //! * `rb_ascii8bit_encindex`: [`encoding::Index::ascii8bit`]. //! * `rb_ascii8bit_encoding`: //! [`RbEncoding::ascii8bit`](encoding::RbEncoding::ascii8bit). // * `rb_assert_failure`: // * `rb_assoc_new`: //! * `rb_attr`: [`Module::define_attr`]. // * `rb_attr_get`: // * `rb_autoload`: // * `rb_autoload_load`: // * `rb_autoload_p`: //! //! ## `rb_b` //! //! * `rb_backref_get`: [`backref_get`]. // * `rb_backref_set`: // * `rb_backtrace`: // * `rb_big2dbl`: // * `rb_big2int`: // * `rb_big2ll`: // * `rb_big2long`: // * `rb_big2str`: // * `rb_big2uint`: // * `rb_big2ull`: // * `rb_big2ulong`: // * `rb_bigzero_p`: // * `rb_big_2comp`: // * `rb_big_and`: // * `rb_big_clone`: // * `rb_big_cmp`: // * `rb_big_div`: // * `rb_big_divmod`: // * `rb_big_eq`: // * `rb_big_eql`: // * `rb_big_idiv`: // * `rb_big_lshift`: // * `rb_big_minus`: // * `rb_big_modulo`: // * `rb_big_mul`: // * `rb_big_new`: //! * `rb_big_norm`: [`Integer::norm`]. // * `rb_big_or`: // * `rb_big_pack`: // * `rb_big_plus`: // * `rb_big_pow`: // * `rb_big_resize`: // * `rb_big_rshift`: // * `rb_big_sign`: // * `rb_big_unpack`: // * `rb_big_xor`: //! * `rb_block_call`: See [`Value::block_call`]. //! * `rb_block_call_kw`: [`Value::block_call`]. //! * `rb_block_given_p`: [`block::block_given`]. // * `rb_block_lambda`: //! * `rb_block_proc`: [`block::block_proc`]. //! * `rb_bug`: [`error::bug`]. // * `rb_bug_errno`: // * `RB_BUILTIN_TYPE`: //! //! ## `rb_c` //! //! * `rb_call_super`: See [`call_super`]. //! * `rb_call_super_kw`: [`call_super`]. // * `rb_catch`: // * `rb_catch_obj`: // * `rb_category_compile_warn`: // * `rb_category_warn`: // * `rb_category_warning`: // * `rb_char_to_option_kcode`: //! * `rb_check_arity`: [`scan_args::check_arity`]. //! * `rb_check_array_type`: See [`TryConvert`] and [`Value::try_convert`]. // * `rb_check_convert_type`: // * `rb_check_copyable`: // * `rb_check_frozen`: // * `rb_check_frozen_inline`: //! * `rb_check_funcall`: See [`Value::check_funcall`]. //! * `rb_check_funcall_kw`: [`Value::check_funcall`]. //! * `rb_check_hash_type`: See [`TryConvert`] and [`Value::try_convert`]. //! * `rb_check_id`: Similar to [`Id::check`](value::Id::check). //! * `rb_check_id_cstr`: [`Id::check`](value::Id::check). // * `rb_check_inheritable`: // * `rb_check_safe_str`: // * `rb_check_string_type`: //! * `rb_check_symbol`: Similar to [`StaticSymbol::check`]. //! * `rb_check_symbol_cstr`: [`StaticSymbol::check`]. // * `rb_check_to_float`: // * `rb_check_to_int`: // * `rb_check_to_integer`: // * `rb_check_type`: //! * `rb_check_typeddata`: See [`TryConvert`] and [`Value::try_convert`]. // * `RB_CHR2FIX`: //! * `rb_class2name`: [`RClass::name`]. // * `rb_class_descendants`: // * `rb_class_get_superclass`: // * `rb_class_inherited_p`: [`Module::is_inherited`]. // * `rb_class_instance_methods`: //! * `rb_class_name`: Simmilar to [`Value::classname`]. //! * `rb_class_new`: [`RClass::new`]. //! * `rb_class_new_instance`: See [`RClass::new_instance`]. //! * `rb_class_new_instance_kw`: [`RClass::new_instance`]. // * `rb_class_new_instance_pass_kw`: // * `rb_class_of`: // * `rb_class_path`: // * `rb_class_path_cached`: // * `rb_class_private_instance_methods`: // * `rb_class_protected_instance_methods`: // * `rb_class_public_instance_methods`: // * `rb_class_real`: // * `rb_class_subclasses`: //! * `rb_class_superclass`: [`RClass::superclass`]. // * `rb_clear_constant_cache`: // * `rb_clear_trace_func`: // * `rb_cloexec_dup`: // * `rb_cloexec_dup2`: // * `rb_cloexec_fcntl_dupfd`: // * `rb_cloexec_open`: // * `rb_cloexec_pipe`: // * `rb_close_before_exec`: // * `rb_cmperr`: // * `rb_cmpint`: // * `rb_compile_error`: // * `rb_compile_error_append`: // * `rb_compile_error_with_enc`: // * `rb_compile_warn`: // * `rb_compile_warning`: // * `rb_Complex`: // * `rb_Complex1`: // * `rb_Complex2`: //! * `rb_complex_abs`: [`RComplex::abs`]. // * `rb_complex_add`: //! * `rb_complex_arg`: [`RComplex::arg`]. //! * `rb_complex_conjugate`: [`RComplex::conjugate`]. // * `rb_complex_div`: //! * `rb_complex_imag`: [`RComplex::imag`]. // * `rb_complex_minus`: // * `rb_complex_mul`: // * `rb_complex_nagate`: //! * `rb_complex_new`: [`RComplex::new`]. // * `rb_complex_new1`: // * `rb_complex_new2`: //! * `rb_complex_new_polar`: [`RComplex::polar`]. // * `rb_complex_plus`: // * `rb_complex_pow`: // * `rb_complex_raw`: // * `rb_complex_raw1`: // * `rb_complex_raw2`: //! * `rb_complex_real`: [`RComplex::real`]. // * `rb_complex_sub`: // * `rb_complex_uminus`: // * `rb_const_defined`: // * `rb_const_defined_at`: // * `rb_const_defined_from`: //! * `rb_const_get`: [`Module::const_get`]. // * `rb_const_get_at`: // * `rb_const_get_from`: // * `rb_const_list`: // * `rb_const_remove`: //! * `rb_const_set`: [`Module::const_set`]. // * `rb_convert_type`: // * `rb_copy_generic_ivar`: // * `rb_cstr2inum`: // * `rb_cstr_to_dbl`: // * `rb_cstr_to_inum`: //! * `rb_current_receiver`: [`current_receiver`]. // * `rb_cvar_defined`: // * `rb_cvar_find`: // * `rb_cvar_get`: // * `rb_cvar_set`: // * `rb_cv_get`: // * `rb_cv_set`: //! //! ## `rb_d` //! //! * `rb_data_define`: [`Ruby::define_data`]. //! * `rb_data_object_make`: See [`wrap`] and [`TypedData`]. //! * `rb_data_object_wrap`: See [`wrap`] and [`TypedData`]. //! * `rb_data_object_zalloc`: See [`wrap`] and [`TypedData`]. //! * `rb_data_typed_object_make`: See [`wrap`] and [`TypedData`]. //! * `rb_data_typed_object_wrap`: See [`wrap`] and [`TypedData`]. //! * `rb_data_typed_object_zalloc`: See [`wrap`] and [`TypedData`]. // * `rb_dbl2big`: // * `rb_dbl_cmp`: // * `rb_dbl_complex_new`: // * `rb_debug_inspector_backtrace_locations`: // * `rb_debug_inspector_frame_binding_get`: // * `rb_debug_inspector_frame_class_get`: // * `rb_debug_inspector_frame_iseq_get`: // * `rb_debug_inspector_frame_self_get`: // * `rb_debug_inspector_open`: // * `rb_debug_rstring_null_ptr`: //! * `rb_default_external_encoding`: //! [`RbEncoding::default_external`](encoding::RbEncoding::default_external). //! * `rb_default_internal_encoding`: //! [`RbEncoding::default_internal`](encoding::RbEncoding::default_internal). //! * `rb_define_alias`: [`Module::define_alias`]. // * `rb_define_alloc_func`: //! * `rb_define_attr`: See [`Module::define_attr`]. //! * `rb_define_class`: [`define_class`]. //! * `rb_define_class_id`: Simmilar to [`define_class`]. //! * `rb_define_class_id_under`: [`Module::define_class`]. //! * `rb_define_class_under`: See [`Module::define_class`]. // * `rb_define_class_variable`: // * `rb_define_const`: // * `rb_define_dummy_encoding`: // * `rb_define_finalizer`: // * `rb_define_global_const`: //! * `rb_define_global_function`: [`define_global_function`]. // * `rb_define_hooked_variable`: //! * `rb_define_method`: See [`Module::define_method`]. //! * `rb_define_method_id`: [`Module::define_method`]. //! * `rb_define_module`: [`define_module`]. //! * `rb_define_module_function`: [`RModule::define_module_function`]. //! * `rb_define_module_id`: See [`define_module`]. //! * `rb_define_module_id_under`: [`Module::define_module`]. //! * `rb_define_module_under`: See [`Module::define_module`]. //! * `rb_define_private_method`: [`Module::define_private_method`]. //! * `rb_define_protected_method`: [`Module::define_protected_method`]. // * `rb_define_readonly_variable`: //! * `rb_define_singleton_method`: [`Object::define_singleton_method`]. //! * `rb_define_variable`: [`define_variable`]. // * `rb_define_virtual_variable`: // * `rb_deprecate_constant`: // * `rb_detach_process`: // * `rb_dir_getwd`: // * `rb_disable_super`: // * `rb_during_gc`: // * `RB_DYNAMIC_SYM_P`: //! //! ## `rb_e`-`rb_enb` // * `rb_each`: // * `rb_econv_append`: // * `rb_econv_asciicompat_encoding`: // * `rb_econv_binmode`: // * `rb_econv_check_error`: // * `rb_econv_close`: // * `rb_econv_convert`: // * `rb_econv_decorate_at_first`: // * `rb_econv_decorate_at_last`: // * `rb_econv_encoding_to_insert_output`: // * `rb_econv_has_convpath_p`: // * `rb_econv_insert_output`: // * `rb_econv_make_exception`: // * `rb_econv_open`: // * `rb_econv_open_exc`: // * `rb_econv_open_opts`: // * `rb_econv_prepare_options`: // * `rb_econv_prepare_opts`: // * `rb_econv_putback`: // * `rb_econv_putbackable`: // * `rb_econv_set_replacement`: // * `rb_econv_str_append`: // * `rb_econv_str_convert`: // * `rb_econv_substr_append`: // * `rb_econv_substr_convert`: // * `rb_enable_super`: //! //! ## `rb_enc` // * `RB_ENCODING_CODERANGE_SET`: // * `RB_ENCODING_GET`: // * `RB_ENCODING_GET_INLINED`: // * `RB_ENCODING_IS_ASCII8BIT`: // * `RB_ENCODING_SET`: // * `RB_ENCODING_SET_INLINED`: // * `rb_enc_alias`: //! * `rb_enc_ascget`: [`RbEncoding::ascget`](encoding::RbEncoding::ascget). // * `rb_enc_asciicompat`: //! * `rb_enc_associate`: //! See [`EncodingCapable::enc_associate`](encoding::EncodingCapable::enc_associate). //! * `rb_enc_associate_index`: //! [`EncodingCapable::enc_associate`](encoding::EncodingCapable::enc_associate). // * `rb_enc_capable`: //! * `rb_enc_check`: [`encoding::check`]. //! * `rb_enc_codelen`: [`RbEncoding::codelen`](encoding::RbEncoding::codelen). //! * `rb_enc_codepoint_len`: //! [`RbEncoding::codepoint_len`](encoding::RbEncoding::codepoint_len). //! * `RB_ENC_CODERANGE`: [`RString::enc_coderange`]. // * `RB_ENC_CODERANGE_AND`: // * `RB_ENC_CODERANGE_ASCIIONLY`: // * `RB_ENC_CODERANGE_CLEAN_P`: //! * `RB_ENC_CODERANGE_CLEAR`: [`RString::enc_coderange_clear`]. //! * `RB_ENC_CODERANGE_SET`: [`RString::enc_coderange_set`]. // * `rb_enc_code_to_mbclen`: //! * `rb_enc_compatible`: [`encoding::compatible`]. //! * `rb_enc_copy`: [`encoding::copy`]. //! * `rb_enc_default_external`: //! [`Encoding::default_external`](encoding::Encoding::default_external). // * `rb_enc_default_internal`: //! [`Encoding::default_internal`](encoding::Encoding::default_internal). // * `rb_enc_dummy_p`: //! * `rb_enc_fast_mbclen`: //! [`RbEncoding::fast_mbclen`](encoding::RbEncoding::fast_mbclen). //! * `rb_enc_find`: [`RbEncoding::find`](encoding::RbEncoding::find). //! * `rb_enc_find_index`: [`encoding::Index::find`]. //! * `rb_enc_from_encoding`: [`std::convert::From`]. //! * `rb_enc_from_index`: [`std::convert::From`]. //! * `rb_enc_get`: Use [`EncodingCapable::enc_get`](encoding::EncodingCapable::enc_get) //! plus [`std::convert::From`]. //! * `rb_enc_get_index`: //! [`EncodingCapable::enc_get`](encoding::EncodingCapable::enc_get). // * `rb_enc_interned_str`: // * `rb_enc_interned_str_cstr`: // * `rb_enc_isalnum`: // * `rb_enc_isalpha`: // * `rb_enc_isascii`: // * `rb_enc_isctype`: // * `rb_enc_isdigit`: // * `rb_enc_islower`: // * `rb_enc_isprint`: // * `rb_enc_ispunct`: // * `rb_enc_isspace`: // * `rb_enc_isupper`: // * `rb_enc_is_newline`: // * `rb_enc_left_char_head`: //! * `rb_enc_mbclen`: [`RbEncoding::mbclen`](encoding::RbEncoding::mbclen). // * `rb_enc_mbcput`: // * `rb_enc_mbc_to_codepoint`: // * `rb_enc_mbmaxlen`: // * `rb_enc_mbminlen`: // * `rb_enc_name`: // * `rb_enc_nth`: // * `rb_enc_path_end`: // * `rb_enc_path_last_separator`: // * `rb_enc_path_next`: // * `rb_enc_path_skip_prefix`: //! * `rb_enc_precise_mbclen`: //! [`RbEncoding::precise_mbclen`](encoding::RbEncoding::precise_mbclen). // * `rb_enc_prev_char`: // * `rb_enc_raise`: //! * `rb_enc_reg_new`: [`RRegexp::new`]. // * `rb_enc_replicate`: // * `rb_enc_right_char_head`: // * `rb_enc_set_default_external`: // * `rb_enc_set_default_internal`: //! * `rb_enc_set_index`: //! [`EncodingCapable::enc_set`](encoding::EncodingCapable::enc_set). // * `rb_enc_sprintf`: // * `rb_enc_step_back`: // * `rb_enc_strlen`: // * `rb_enc_str_asciicompat_p`: // * `rb_enc_str_asciionly_p`: // * `rb_enc_str_buf_cat`: //! * `rb_enc_str_coderange`: [`RString::enc_coderange_scan`]. //! * `rb_enc_str_new`: [`RString::enc_new`]. // * `rb_enc_str_new_cstr`: // * `rb_enc_str_new_lit`: // * `rb_enc_str_new_literal`: // * `rb_enc_str_new_static`: // * `rb_enc_symname2_p`: // * `rb_enc_symname_p`: // * `rb_enc_tolower`: // * `rb_enc_toupper`: //! * `rb_enc_to_index`: [`std::convert::From`]. //! * `rb_enc_uint_chr`: [`RbEncoding::chr`](encoding::RbEncoding::chr). // * `rb_enc_unicode_p`: // * `rb_enc_vsprintf`: //! //! ## `rb_en`-`rb_ez` // * `rb_ensure`: //! * `rb_enumeratorize`: See [`Value::enumeratorize`]. //! * `rb_enumeratorize_with_size`: See [`Value::enumeratorize`]. //! * `rb_enumeratorize_with_size_kw`: [`Value::enumeratorize`]. // * `rb_enum_values_pack`: // * `rb_env_clear`: // * `rb_eof_error`: //! * `rb_eql`: [`Value::eql`]. //! * `rb_equal`: [`Value::equal`]. // * `rb_errinfo`: //! * `rb_error_arity`: [`scan_args::check_arity`]. // * `rb_error_frozen`: // * `rb_error_frozen_object`: // * `rb_eval_cmd_kw`: //! * `rb_eval_string`: See [`eval()`] or [`eval!`]. //! * `rb_eval_string_protect`: [`eval()`] or [`eval!`]. // * `rb_eval_string_wrap`: // * `rb_exc_fatal`: // * `rb_exc_new`: // * `rb_exc_new_cstr`: // * `rb_exc_new_str`: //! * `rb_exc_raise`: Return [`Error`]. // * `rb_exec_end_proc`: // * `rb_exec_recursive`: // * `rb_exec_recursive_outer`: // * `rb_exec_recursive_paired`: // * `rb_exec_recursive_paired_outer`: // * `rb_exit`: //! * `rb_extend_object`: [`Object::extend_object`]. // * `rb_external_str_new`: // * `rb_external_str_new_cstr`: // * `rb_external_str_new_with_enc`: // * `rb_extract_keywords`: // * `RB_EXT_RACTOR_SAFE`: // * `rb_ext_ractor_safe`: //! //! ## `rb_f` // * `rb_fatal`: // * `rb_fdopen`: // * `rb_fd_clr`: // * `rb_fd_copy`: // * `rb_fd_dup`: // * `rb_fd_fix_cloexec`: // * `rb_fd_init`: // * `rb_fd_isset`: // * `rb_fd_max`: // * `rb_fd_ptr`: // * `rb_fd_resize`: // * `rb_fd_select`: // * `rb_fd_set`: // * `rb_fd_term`: // * `rb_fd_zero`: // * `rb_feature_provided`: //! * `rb_fiber_alive_p`: [`Fiber::is_alive`]. //! * `rb_fiber_current`: [`Ruby::fiber_current`] //! * `rb_fiber_new`: See [`Ruby::fiber_new`] & [`Ruby::fiber_new_from_fn`]. //! * `rb_fiber_new_storage`: [`Ruby::fiber_new`] & [`Ruby::fiber_new_from_fn`]. //! * `rb_fiber_raise`: [`Fiber::raise`]. //! * `rb_fiber_resume`: See [`Fiber::resume`]. //! * `rb_fiber_resume_kw`: [`Fiber::resume`]. // * `rb_fiber_scheduler_address_resolve`: // * `rb_fiber_scheduler_block`: // * `rb_fiber_scheduler_close`: // * `rb_fiber_scheduler_current`: // * `rb_fiber_scheduler_current_for_thread`: // * `rb_fiber_scheduler_get`: // * `rb_fiber_scheduler_io_close`: // * `rb_fiber_scheduler_io_pread`: // * `rb_fiber_scheduler_io_pwrite`: // * `rb_fiber_scheduler_io_read`: // * `rb_fiber_scheduler_io_read_memory`: // * `rb_fiber_scheduler_io_result`: // * `rb_fiber_scheduler_io_result_apply`: // * `rb_fiber_scheduler_io_wait`: // * `rb_fiber_scheduler_io_wait_readable`: // * `rb_fiber_scheduler_io_wait_writable`: // * `rb_fiber_scheduler_io_write`: // * `rb_fiber_scheduler_io_write_memory`: // * `rb_fiber_scheduler_kernel_sleep`: // * `rb_fiber_scheduler_kernel_sleepv`: // * `rb_fiber_scheduler_make_timeout`: // * `rb_fiber_scheduler_process_wait`: // * `rb_fiber_scheduler_set`: // * `rb_fiber_scheduler_unblock`: //! * `rb_fiber_transfer`: See [`Fiber::transfer`]. //! * `rb_fiber_transfer_kw`: [`Fiber::transfer`]. //! * `rb_fiber_yield`: See [`Ruby::fiber_yield`]. //! * `rb_fiber_yield_kw`: [`Ruby::fiber_yield`]. //! * `rb_filesystem_encindex`: [`encoding::Index::filesystem`]. //! * `rb_filesystem_encoding`: //! [`RbEncoding::filesystem`](encoding::RbEncoding::filesystem). // * `rb_filesystem_str_new`: // * `rb_filesystem_str_new_cstr`: // * `rb_file_absolute_path`: // * `rb_file_directory_p`: // * `rb_file_dirname`: // * `rb_file_expand_path`: // * `rb_file_open`: // * `rb_file_open_str`: // * `rb_file_size`: // * `rb_file_s_absolute_path`: // * `rb_file_s_expand_path`: //! * `rb_find_encoding`: [`std::convert::From`]. // * `rb_find_file`: // * `rb_find_file_ext`: // * `RB_FIX2INT`: // * `rb_fix2int`: // * `RB_FIX2LONG`: // * `rb_fix2long`: // * `RB_FIX2SHORT`: // * `rb_fix2short`: // * `rb_fix2str`: // * `RB_FIX2UINT`: // * `rb_fix2uint`: // * `RB_FIX2ULONG`: // * `rb_fix2ulong`: // * `rb_fix2ushort`: // * `RB_FIXABLE`: // * `RB_FIXNUM_P`: // * `rb_fix_new`: // * `rb_Float`: //! * `rb_float_new`: [`RFloat::from_f64`] or [`Float::from_f64`]. //! * `rb_float_new_in_heap`: See [`Float::from_f64`]. // * `RB_FLOAT_TYPE_P`: // * `rb_float_value`: [`RFloat::to_f64`] or [`Float::to_f64`]. // * `RB_FLONUM_P`: //! * `rb_flt_rationalize`: [`Float::rationalize`]. //! * `rb_flt_rationalize_with_prec`: [`Float::rationalize_with_prec`]. // * `RB_FL_ABLE`: // * `RB_FL_ALL`: // * `RB_FL_ALL_RAW`: // * `RB_FL_ANY`: // * `RB_FL_ANY_RAW`: // * `RB_FL_REVERSE`: // * `RB_FL_REVERSE_RAW`: // * `RB_FL_SET`: // * `RB_FL_SET_RAW`: // * `RB_FL_TEST`: // * `RB_FL_TEST_RAW`: // * `RB_FL_UNSET`: // * `RB_FL_UNSET_RAW`: // * `rb_frame_callee`: // * `rb_frame_method_id_and_class`: // * `rb_frame_this_func`: // * `rb_freeze_singleton_class`: // * `rb_free_generic_ivar`: // * `rb_free_tmp_buffer`: // * `rb_frozen_class_p`: // * `rb_frozen_error_raise`: //! * `rb_funcall`: See [`Value::funcall`]. //! * `rb_funcallv`: See [`Value::funcall`]. //! * `rb_funcallv_kw`: [`Value::funcall`]. //! * `rb_funcallv_public`: See [`Value::funcall_public`]. //! * `rb_funcallv_public_kw`: [`Value::funcall_public`]. // * `rb_funcall_passing_block`: // * `rb_funcall_passing_block_kw`: //! * `rb_funcall_with_block`: See [`Value::funcall_with_block`]. //! * `rb_funcall_with_block_kw`: [`Value::funcall_with_block`]. // * `rb_f_abort`: // * `rb_f_exec`: // * `rb_f_exit`: // * `rb_f_global_variables`: // * `rb_f_kill`: // * `rb_f_notimplement`: // * `rb_f_require`: // * `rb_f_sprintf`: // * `rb_f_trace_var`: // * `rb_f_untrace_var`: //! //! ## `rb_g` //! //! * `rb_gc`: [`gc::start`]. //! * `rb_gc_adjust_memory_usage`: [`gc::adjust_memory_usage`]. // * `rb_gc_call_finalizer_at_exit`: // * `rb_gc_copy_finalizer`: //! * `rb_gc_count`: [`gc::count`]. //! * `rb_gc_disable`: [`gc::disable`]. //! * `rb_gc_enable`: [`gc::enable`]. // * `RB_GC_GUARD`: // * `rb_gc_latest_gc_info`: //! * `rb_gc_location`: [`gc::Compactor::location`]. //! * `rb_gc_mark`: [`gc::Marker::mark`]. //! * `rb_gc_mark_locations`: [`gc::Marker::mark_slice`]. // * `rb_gc_mark_maybe`: //! * `rb_gc_mark_movable`: [`gc::Marker::mark_movable`]. //! * `rb_gc_register_address`: [`gc::register_address`] or //! [`BoxValue`](value::BoxValue). //! * `rb_gc_register_mark_object`: [`gc::register_mark_object`]. //! * `rb_gc_start`: [`gc::start`]. //! * `rb_gc_stat`: [`gc::stat`] or [`gc::all_stats`]. //! * `rb_gc_unregister_address`: [`gc::unregister_address`]. // * `rb_gc_update_tbl_refs`: // * `rb_gc_writebarrier`: // * `rb_gc_writebarrier_unprotect`: // * `rb_generic_ivar_table`: // * `rb_genrand_int32`: // * `rb_genrand_real`: // * `rb_genrand_ulong_limited`: // * `rb_gets`: // * `rb_get_alloc_func`: // * `rb_get_argv`: //! * `rb_get_kwargs`: [`scan_args::get_kwargs`]. //! * `rb_get_path`: [`TryConvert`]/[`Value::try_convert`] to [`std::path::PathBuf`]. // * `rb_get_values_at`: // * `rb_glob`: // * `rb_global_variable`: // * `rb_gvar_readonly_setter`: // * `rb_gvar_val_getter`: // * `rb_gvar_val_marker`: // * `rb_gvar_val_setter`: // * `rb_gv_get`: // * `rb_gv_set`: //! //! # `rb_h` // * `rb_Hash`: //! * `rb_hash`: [`Value::hash`]. //! * `rb_hash_aref`: [`RHash::aref`]. //! * `rb_hash_aset`: [`RHash::aset`]. //! * `rb_hash_bulk_insert`: [`RHash::bulk_insert`]. // * `rb_hash_bulk_insert_into_st_table`: //! * `rb_hash_clear`: [`RHash::clear`]. //! * `rb_hash_delete`: [`RHash::delete`]. // * `rb_hash_delete_if`: // * `rb_hash_dup`: // * `rb_hash_end`: //! * `rb_hash_fetch`: [`RHash::fetch`]. //! * `rb_hash_foreach`: [`RHash::foreach`]. // * `rb_hash_freeze`: See [`Value::freeze`]. // * `rb_hash_ifnone`: // * `rb_hash_iter_lev`: //! * `rb_hash_lookup`: [`RHash::lookup`]. //! * `rb_hash_lookup2`: [`RHash::lookup2`]. //! * `rb_hash_new`: [`RHash::new`]. //! * `rb_hash_new_capa`: [`RHash::with_capacity`]. // * `rb_hash_set_ifnone`: //! * `rb_hash_size`: [`RHash::size`]. //! * `rb_hash_size_num`: [`RHash::len`]. // * `rb_hash_start`: // * `rb_hash_tbl`: // * `rb_hash_uint`: // * `rb_hash_uint32`: //! * `rb_hash_update_by`: [`RHash::update`] (`update_func` arg not implemented). //! //! ## `rb_i`-`rb_in` //! //! * `rb_id2name`: [`Id::name`](value::Id::name). // * `rb_id2str`: // * `RB_ID2SYM`: //! * `rb_id2sym`: [`std::convert::From`]. // * `rb_id_attrset`: // * `RB_IMMEDIATE_P`: //! * `rb_include_module`: [`Module::include_module`]. //! * `rb_inspect`: [`Value::inspect`] or [`std::fmt::Debug`]. // * `rb_int2big`: // * `RB_INT2FIX`: // * `rb_int2inum`: // * `RB_INT2NUM`: // * `rb_int2num_inline`: // * `rb_Integer`: // * `rb_integer_pack`: // * `rb_integer_type_p`: // * `rb_integer_unpack`: //! * `rb_intern`: [`std::convert::From`]. //! * `rb_intern2`: [`std::convert::From`]. //! * `rb_intern3`: [`std::convert::From`]. // * `rb_interned_str`: // * `rb_interned_str_cstr`: // * `rb_intern_const`: //! * `rb_intern_str`: [`std::convert::From`]. // * `rb_interrupt`: // * `rb_int_new`: // * `rb_int_pair_to_real`: // * `rb_int_positive_pow`: // * `rb_invalid_str`: //! //! ## `rb_io` // * `rb_io_addstr`: // * `rb_io_ascii8bit_binmode`: // * `rb_io_binmode`: // * `rb_io_bufwrite`: // * `rb_io_check_byte_readable`: // * `rb_io_check_char_readable`: // * `rb_io_check_closed`: // * `rb_io_check_initialized`: // * `rb_io_check_io`: // * `rb_io_check_readable`: // * `rb_io_check_writable`: // * `rb_io_close`: // * `rb_io_descriptor`: // * `rb_io_eof`: // * `rb_io_extract_encoding_option`: // * `rb_io_extract_modeenc`: // * `rb_io_fdopen`: // * `rb_io_flush`: // * `rb_io_fptr_finalize`: // * `rb_io_getbyte`: // * `rb_io_gets`: // * `rb_io_get_io`: // * `rb_io_get_write_io`: // * `rb_io_make_open_file`: // * `rb_io_maybe_wait`: // * `rb_io_maybe_wait_readable`: // * `rb_io_maybe_wait_writable`: // * `rb_io_modestr_fmode`: // * `rb_io_modestr_oflags`: // * `rb_io_oflags_fmode`: // * `RB_IO_OPEN`: // * `RB_IO_POINTER`: // * `rb_io_print`: // * `rb_io_printf`: // * `rb_io_puts`: // * `rb_io_read_check`: // * `rb_io_read_pending`: // * `rb_io_set_nonblock`: // * `rb_io_set_write_io`: // * `rb_io_stdio_file`: // * `rb_io_synchronized`: // * `rb_io_ungetbyte`: // * `rb_io_ungetc`: // * `rb_io_wait`: // * `rb_io_write`: //! //! ## `rb_is`-`rb_iz` // * `rb_isalnum`: // * `rb_isalpha`: // * `rb_isascii`: // * `rb_isblank`: // * `rb_iscntrl`: // * `rb_isdigit`: // * `rb_isgraph`: // * `rb_islower`: // * `rb_isprint`: // * `rb_ispunct`: // * `rb_isspace`: // * `rb_isupper`: // * `rb_isxdigit`: // * `rb_is_absolute_path`: // * `rb_is_attrset_id`: // * `rb_is_class_id`: // * `rb_is_const_id`: // * `rb_is_global_id`: // * `rb_is_instance_id`: // * `rb_is_junk_id`: // * `rb_is_local_id`: //! * `rb_iter_break`: See [`Error::iter_break`]. //! * `rb_iter_break_value`: [`Error::iter_break`]. // * `rb_ivar_count`: // * `rb_ivar_defined`: // * `rb_ivar_foreach`: //! * `rb_ivar_get`: [`Object::ivar_get`]. //! * `rb_ivar_set`: [`Object::ivar_set`]. // * `rb_iv_get`: // * `rb_iv_set`: //! //! ## `rb_j`-`rb_k` //! //! * `rb_jump_tag`: Return [`Error`]. // * `rb_keyword_given_p`: //! //! ## `rb_l` // * `rb_lastline_get`: // * `rb_lastline_set`: // * `rb_last_status_get`: // * `rb_last_status_set`: // * `RB_LIKELY`: // * `rb_ll2inum`: // * `RB_LL2NUM`: // * `rb_ll2num_inline`: // * `rb_load`: // * `rb_loaderror`: // * `rb_loaderror_with_path`: // * `rb_load_file`: // * `rb_load_file_str`: // * `rb_load_protect`: // * `rb_locale_charmap`: //! * `rb_locale_encindex`: [`encoding::Index::locale`]. //! * `rb_locale_encoding`: [`RbEncoding::locale`](encoding::RbEncoding::locale). // * `rb_locale_str_new`: // * `rb_locale_str_new_cstr`: // * `RB_LONG2FIX`: // * `rb_long2int`: // * `rb_long2int_inline`: // * `RB_LONG2NUM`: // * `rb_long2num_inline`: //! //! ## `rb_m` // * `rb_make_backtrace`: // * `rb_make_exception`: // * `rb_mark_hash`: // * `rb_mark_set`: // * `rb_mark_tbl`: // * `rb_mark_tbl_no_pin`: // * `rb_marshal_define_compat`: // * `rb_marshal_dump`: // * `rb_marshal_load`: // * `rb_match_busy`: // * `rb_memcicmp`: // * `rb_memerror`: // * `rb_memhash`: // * `rb_memory_id`: // * `rb_memory_view_available_p`: // * `rb_memory_view_extract_item_members`: // * `rb_memory_view_fill_contiguous_strides`: // * `rb_memory_view_get`: // * `rb_memory_view_get_item`: // * `rb_memory_view_get_item_pointer`: // * `rb_memory_view_init_as_byte_array`: // * `rb_memory_view_is_column_major_contiguous`: // * `rb_memory_view_is_contiguous`: // * `rb_memory_view_is_row_major_contiguous`: // * `rb_memory_view_item_size_from_format`: // * `rb_memory_view_parse_item_format`: // * `rb_memory_view_prepare_item_desc`: // * `rb_memory_view_register`: // * `rb_memory_view_release`: // * `rb_memsearch`: // * `rb_mem_clear`: // * `rb_method_basic_definition_p`: // * `rb_method_boundp`: // * `rb_method_call`: // * `rb_method_call_kw`: // * `rb_method_call_with_block`: // * `rb_method_call_with_block_kw`: //! * `rb_module_new`: [`RModule::new`]. //! * `rb_mod_ancestors`: [`Module::ancestors`]. // * `rb_mod_class_variables`: // * `rb_mod_constants`: // * `rb_mod_const_at`: // * `rb_mod_const_missing`: // * `rb_mod_const_of`: // * `rb_mod_included_modules`: // * `rb_mod_include_p`: // * `rb_mod_init_copy`: // * `rb_mod_method_arity`: // * `rb_mod_module_eval`: // * `rb_mod_module_exec`: // * `rb_mod_name`: // * `rb_mod_remove_const`: // * `rb_mod_remove_cvar`: // * `rb_mod_syserr_fail`: // * `rb_mod_syserr_fail_str`: // * `rb_mod_sys_fail`: // * `rb_mod_sys_fail_str`: // * `rb_must_asciicompat`: //! * `rb_mutex_lock`: [`Mutex::lock`]. //! * `rb_mutex_locked_p`: [`Mutex::is_locked`]. //! * `rb_mutex_new`: [`Ruby::mutex_new`]. //! * `rb_mutex_sleep`: [`Mutex::sleep`]. //! * `rb_mutex_synchronize`: [`Mutex::synchronize`]. //! * `rb_mutex_trylock`: [`Mutex::trylock`]. //! * `rb_mutex_unlock`: [`Mutex::unlock`]. //! //! ## `rb_n` // * `rb_name_error`: // * `rb_name_error_str`: // * `rb_nativethread_lock_destroy`: // * `rb_nativethread_lock_initialize`: // * `rb_nativethread_lock_lock`: // * `rb_nativethread_lock_unlock`: // * `rb_nativethread_self`: // * `rb_native_cond_broadcast`: // * `rb_native_cond_destroy`: // * `rb_native_cond_initialize`: // * `rb_native_cond_signal`: // * `rb_native_cond_timedwait`: // * `rb_native_cond_wait`: // * `rb_native_mutex_destroy`: // * `rb_native_mutex_initialize`: // * `rb_native_mutex_lock`: // * `rb_native_mutex_trylock`: // * `rb_native_mutex_unlock`: // * `rb_need_block`: // * `RB_NEGFIXABLE`: // * `RB_NEWOBJ`: // * `rb_newobj`: // * `RB_NEWOBJ_OF`: // * `rb_newobj_of`: // * `RB_NIL_P`: // * `rb_nogvl`: // * `rb_notimplement`: // * `rb_num2char_inline`: // * `RB_NUM2CHR`: // * `rb_num2dbl`: // * `rb_num2fix`: // * `RB_NUM2INT`: // * `rb_num2int`: // * `rb_num2int_inline`: // * `RB_NUM2LL`: // * `rb_num2ll`: // * `rb_num2ll_inline`: // * `RB_NUM2LONG`: // * `rb_num2long`: // * `rb_num2long_inline`: // * `RB_NUM2SHORT`: // * `rb_num2short`: // * `rb_num2short_inline`: // * `RB_NUM2SIZE`: // * `RB_NUM2SSIZE`: // * `RB_NUM2UINT`: // * `rb_num2uint`: // * `RB_NUM2ULL`: // * `rb_num2ull`: // * `rb_num2ull_inline`: // * `RB_NUM2ULONG`: // * `rb_num2ulong`: // * `rb_num2ulong_inline`: // * `RB_NUM2USHORT`: // * `rb_num2ushort`: //! * `rb_num_coerce_bin`: [`Numeric::coerce_bin`]. //! * `rb_num_coerce_bit`: [`Numeric::coerce_bit`]. //! * `rb_num_coerce_cmp`: [`Numeric::coerce_cmp`]. //! * `rb_num_coerce_relop`: [`Numeric::coerce_relop`]. // * `rb_num_zerodiv`: //! //! ## `rb_o` // * `rb_obj_alloc`: //! * `rb_obj_as_string`: [`Value::to_r_string`]. // * `rb_obj_call_init`: // * `rb_obj_call_init_kw`: // * `rb_obj_class`: //! * `rb_obj_classname`: [`Value::classname`]. // * `rb_obj_clone`: // * `rb_obj_dup`: // * `rb_obj_encoding`: // * `RB_OBJ_FREEZE`: //! * `rb_obj_freeze`: [`Value::freeze`]. // * `rb_obj_freeze_inline`: // * `RB_OBJ_FREEZE_RAW`: // * `RB_OBJ_FROZEN`: // * `rb_obj_frozen_p`: // * `RB_OBJ_FROZEN_RAW`: // * `rb_obj_hide`: // * `rb_obj_id`: // * `RB_OBJ_INIT_COPY`: // * `rb_obj_init_copy`: // * `rb_obj_instance_eval`: // * `rb_obj_instance_exec`: // * `rb_obj_instance_variables`: //! * `rb_obj_is_fiber`: [`Fiber::from_value`]. // * `rb_obj_is_instance_of`: //! * `rb_obj_is_kind_of`: [`Value::is_kind_of`]. // * `rb_obj_is_method`: //! * `rb_obj_is_proc`: [`Proc::from_value`](block::Proc::from_value). // * `rb_obj_method`: // * `rb_obj_method_arity`: // * `RB_OBJ_PROMOTED`: // * `RB_OBJ_PROMOTED_RAW`: // * `rb_obj_remove_instance_variable`: //! * `rb_obj_respond_to`: [`Value::respond_to`]. // * `rb_obj_reveal`: // * `rb_obj_setup`: // * `RB_OBJ_SHAREABLE_P`: // * `rb_obj_singleton_methods`: // * `RB_OBJ_WB_UNPROTECT`: // * `rb_obj_wb_unprotect`: // * `RB_OBJ_WB_UNPROTECT_FOR`: // * `RB_OBJ_WRITE`: // * `RB_OBJ_WRITTEN`: // * `rb_out_of_int`: //! //! ## `rb_p` // * `rb_p`: // * `rb_path2class`: // * `rb_path_check`: // * `rb_path_to_class`: // * `rb_pipe`: // * `RB_POSFIXABLE`: // * `rb_postponed_job_register`: // * `rb_postponed_job_register_one`: // * `rb_prepend_module`: [`Module::prepend_module`]. //! * `rb_proc_arity`: [`Proc::arity`](block::Proc::arity). //! * `rb_proc_call`: See [`Proc::call`](block::Proc::call). //! * `rb_proc_call_kw`: [`Proc::call`](block::Proc::call). // * `rb_proc_call_with_block`: // * `rb_proc_call_with_block_kw`: // * `rb_proc_exec`: //! * `rb_proc_lambda_p`: [`Proc::is_lambda`](block::Proc::is_lambda). //! * `rb_proc_new`: [`Proc::new`](block::Proc::new) & [`Proc::from_fn`](block::Proc::from_fn). // * `rb_proc_times`: // * `rb_profile_frames`: // * `rb_profile_frame_absolute_path`: // * `rb_profile_frame_base_label`: // * `rb_profile_frame_classpath`: // * `rb_profile_frame_first_lineno`: // * `rb_profile_frame_full_label`: // * `rb_profile_frame_label`: // * `rb_profile_frame_method_name`: // * `rb_profile_frame_path`: // * `rb_profile_frame_qualified_method_name`: // * `rb_profile_frame_singleton_method_p`: //! * `rb_protect`: Called internally by Magnus when required. Available as //! [`rb_sys::protect`] with `rb-sys` feature for calling raw Ruby api. // * `rb_provide`: // * `rb_provided`: //! //! ## `rb_r` // * `rb_ractor_local_storage_ptr`: // * `rb_ractor_local_storage_ptr_newkey`: // * `rb_ractor_local_storage_ptr_set`: // * `rb_ractor_local_storage_value`: // * `rb_ractor_local_storage_value_lookup`: // * `rb_ractor_local_storage_value_newkey`: // * `rb_ractor_local_storage_value_set`: // * `rb_ractor_make_shareable`: // * `rb_ractor_make_shareable_copy`: // * `rb_ractor_shareable_p`: // * `rb_ractor_stderr`: // * `rb_ractor_stderr_set`: // * `rb_ractor_stdin`: // * `rb_ractor_stdin_set`: // * `rb_ractor_stdout`: // * `rb_ractor_stdout_set`: //! * `rb_raise`: Simmilar to returning [`Error`]. // * `rb_random_base_init`: // * `rb_random_bytes`: // * `RB_RANDOM_DATA_INIT_PARENT`: // * `rb_random_int32`: // * `RB_RANDOM_INTERFACE_DECLARE`: // * `RB_RANDOM_INTERFACE_DECLARE_WITH_REAL`: // * `RB_RANDOM_INTERFACE_DEFINE`: // * `RB_RANDOM_INTERFACE_DEFINE_WITH_REAL`: // * `rb_random_mark`: // * `RB_RANDOM_PARENT`: // * `rb_random_real`: // * `rb_random_ulong_limited`: // * `rb_rand_bytes_int32`: // * `rb_rand_if`: //! * `rb_range_beg_len`: [`Range::beg_len`]. // * `rb_range_new`: [`Range::new`]. // * `rb_range_values`: // * `rb_Rational`: // * `rb_Rational1`: // * `rb_Rational2`: //! * `rb_rational_den`: [`RRational::den`]. //! * `rb_rational_new`: [`RRational::new`]. // * `rb_rational_new1`: // * `rb_rational_new2`: //! * `rb_rational_num`: [`RRational::num`]. // * `rb_rational_raw`: // * `rb_rational_raw1`: // * `rb_rational_raw2`: // * `rb_readwrite_syserr_fail`: // * `rb_readwrite_sys_fail`: // * `RB_REALLOC_N`: // * `rb_refinement_new`: // * `rb_reg_adjust_startpos`: // * `rb_reg_alloc`: //! * `rb_reg_backref_number`: [`RMatch::backref_number`]. // * `rb_reg_init_str`: //! * `rb_reg_last_match`: [`RMatch::matched`]. //! * `rb_reg_match`: [`RRegexp::reg_match`]. // * `rb_reg_match2`: //! * `rb_reg_match_last`: [`RMatch::last`]. //! * `rb_reg_match_post`: [`RMatch::post`]. //! * `rb_reg_match_pre`: [`RMatch::pre`]. //! * `rb_reg_new`: See [`RRegexp::new`]. //! * `rb_reg_new_str`: [`RRegexp::new_str`]. //! * `rb_reg_nth_defined`: [`RMatch::nth_defined`]. //! * `rb_reg_nth_match`: [`RMatch::nth_match`]. //! * `rb_reg_options`: [`RRegexp::options`]. // * `rb_reg_prepare_re`: // * `rb_reg_quote`: // * `rb_reg_regcomp`: // * `rb_reg_region_copy`: // * `rb_reg_regsub`: // * `rb_reg_search`: // * `rb_remove_event_hook`: // * `rb_remove_event_hook_with_data`: // * `rb_remove_method`: // * `rb_remove_method_id`: //! * `rb_require`: [`require`]. //! * `rb_require_string`: [`require`]. // * `rb_rescue`: // * `rb_rescue2`: // * `RB_RESERVED_FD_P`: // * `rb_reserved_fd_p`: // * `rb_reset_random_seed`: // * `rb_respond_to`: // * `rb_ruby_debug_ptr`: // * `rb_ruby_verbose_ptr`: //! //! # `rb_s`-`rb_strl` //! //! * `rb_scan_args`: [`scan_args::scan_args`]. // * `rb_scan_args_bad_format`: // * `rb_scan_args_kw`: // * `rb_scan_args_length_mismatch`: // * `rb_set_class_path`: // * `rb_set_class_path_string`: // * `rb_set_end_proc`: // * `rb_set_errinfo`: //! * `rb_singleton_class`: [`Object::singleton_class`]. // * `rb_singleton_class_attached`: // * `rb_singleton_class_clone`: // * `RB_SIZE2NUM`: // * `rb_sourcefile`: // * `rb_sourceline`: // * `rb_spawn`: // * `rb_spawn_err`: // * `RB_SPECIAL_CONST_P`: // * `rb_special_const_p`: // * `rb_sprintf`: // * `RB_SSIZE2NUM`: // * `RB_ST2FIX`: // * `RB_STATIC_SYM_P`: // * `rb_stat_new`: // * `rb_str2inum`: // * `rb_String`: // * `rb_string_value`: // * `rb_string_value_cstr`: // * `rb_string_value_ptr`: // * `rb_strlen_lit`: //! //! # `rb_struct` // * `rb_struct_alloc`: See [`RClass::new_instance`]. // * `rb_struct_alloc_noinit`: //! * `rb_struct_aref`: [`RStruct::aref`]. //! * `rb_struct_aset`: [`RStruct::aset`]. //! * `rb_struct_define`: [`Ruby::define_struct`]. // * `rb_struct_define_under`: // * `rb_struct_define_without_accessor`: // * `rb_struct_define_without_accessor_under`: //! * `rb_struct_getmember`: [`RStruct::getmember`]. // * `rb_struct_initialize`: //! * `rb_struct_members`: [`RStruct::members`]. //! * `rb_struct_new`: See [`RClass::new_instance`]. // * `rb_struct_ptr`: //! * `rb_struct_size`: [`RStruct::size`]. // * `rb_struct_s_members`: //! //! ## `rb_str` // * `rb_str_append`: //! * `rb_str_buf_append`: [`RString::buf_append`]. //! * `rb_str_buf_cat`: [`RString::cat`]. //! * `rb_str_buf_cat_ascii`: See [`RString::cat`]. //! * `rb_str_buf_new`: [`RString::buf_new`]. //! * `rb_str_buf_new_cstr`: See [`RString::buf_new`] + [`RString::cat`]. //! * `rb_str_capacity`: [`RString::capacity`]. //! * `rb_str_cat`: [`RString::cat`]. // * `rb_str_catf`: //! * `rb_str_cat_cstr`: See [`RString::cat`]. //! * `rb_str_cmp`: [`RString::cmp`]. // * `rb_str_coderange_scan_restartable`: // * `rb_str_comparable`: [`RString::comparable`]. // * `rb_str_concat`: //! * `rb_str_conv_enc`: [`RString::conv_enc`]. // * `rb_str_conv_enc_opts`: //! * `rb_str_drop_bytes`: [`RString::drop_bytes`]. //! * `rb_str_dump`: [`RString::dump`]. // * `rb_str_dup`: //! * `rb_str_dup_frozen`: See [`RString::new_frozen`]. //! * `rb_str_ellipsize`: [`RString::ellipsize`]. // * `rb_str_encode`: // * `rb_str_encode_ospath`: // * `rb_str_equal`: // * `rb_str_export`: // * `rb_str_export_locale`: // * `rb_str_export_to_enc`: // * `rb_str_format`: // * `rb_str_free`: // * `rb_str_freeze`: // * `rb_str_hash`: // * `rb_str_hash_cmp`: // * `rb_str_inspect`: // * `rb_str_intern`: // * `rb_str_length`: // * `rb_str_locktmp`: // * `rb_str_modify`: // * `rb_str_modify_expand`: //! * `rb_str_new`: [`RString::from_slice`]. // * `rb_str_new_cstr`: //! * `rb_str_new_frozen`: [`RString::new_frozen`]. //! * `rb_str_new_lit`: Simmilar to [`r_string!`]. //! * `rb_str_new_literal`: Simmilar to [`r_string!`]. //! * `rb_str_new_shared`: [`RString::new_shared`]. // * `rb_str_new_static`: Simmilar to [`r_string!`]. // * `rb_str_new_with_class`: //! * `rb_str_offset`: [`RString::offset`]. //! * `rb_str_plus`: [`RString::plus`]. //! * `rb_str_replace`: [`RString::replace`]. // * `rb_str_resize`: // * `rb_str_resurrect`: //! * `rb_str_scrub`: [`RString::scrub`]. // * `rb_str_setter`: // * `rb_str_set_len`: //! * `rb_str_shared_replace`: [`RString::shared_replace`]. //! * `rb_str_split`: [`RString::split`]. //! * `rb_str_strlen`: [`RString::length`]. // * `rb_str_sublen`: // * `rb_str_subpos`: // * `rb_str_subseq`: // * `rb_str_substr`: // * `rb_str_succ`: //! * `rb_str_times`: [`RString::times`]. // * `rb_str_tmp_new`: // * `rb_str_to_dbl`: //! * `rb_str_to_interned_str`: [`RString::to_interned_str`]. // * `rb_str_to_inum`: //! * `rb_str_to_str`: [`TryConvert`] or [`Value::try_convert`]. // * `rb_str_unlocktmp`: //! * `rb_str_update`: [`RString::update`]. // * `rb_str_vcatf`: //! //! # `rb_st_` // * `rb_st_add_direct`: // * `rb_st_cleanup_safe`: // * `rb_st_clear`: // * `rb_st_copy`: // * `rb_st_delete`: // * `rb_st_delete_safe`: // * `rb_st_foreach`: // * `rb_st_foreach_check`: // * `rb_st_foreach_safe`: // * `rb_st_foreach_with_replace`: // * `rb_st_free_table`: // * `rb_st_get_key`: // * `rb_st_hash`: // * `rb_st_hash_end`: // * `rb_st_hash_start`: // * `rb_st_hash_uint`: // * `rb_st_hash_uint32`: // * `rb_st_init_numtable`: // * `rb_st_init_numtable_with_size`: // * `rb_st_init_strcasetable`: // * `rb_st_init_strcasetable_with_size`: // * `rb_st_init_strtable`: // * `rb_st_init_strtable_with_size`: // * `rb_st_init_table`: // * `rb_st_init_table_with_size`: // * `rb_st_insert`: // * `rb_st_insert2`: // * `rb_st_keys`: // * `rb_st_keys_check`: // * `rb_st_locale_insensitive_strcasecmp`: // * `rb_st_locale_insensitive_strncasecmp`: // * `rb_st_lookup`: // * `rb_st_memsize`: // * `rb_st_numcmp`: // * `rb_st_numhash`: // * `rb_st_shift`: // * `rb_st_update`: // * `rb_st_values`: // * `rb_st_values_check`: //! //! ## `rb_sy`-`rb_sz` // * `RB_SYM2ID`: //! * `rb_sym2id`: [`std::convert::From`]. //! * `rb_sym2str`: [`Symbol::name`]. // * `RB_SYMBOL_P`: // * `rb_symname_p`: // * `rb_sym_all_symbols`: // * `rb_sym_to_s`: // * `rb_syserr_fail`: // * `rb_syserr_fail_str`: // * `rb_syserr_new`: // * `rb_syserr_new_str`: // * `rb_syswait`: // * `rb_sys_fail`: // * `rb_sys_fail_str`: // * `rb_sys_warning`: //! //! ## `rb_t` //! //! * `RB_TEST`: [`Value::to_bool`] / [`TryConvert`] / [`Value::try_convert`]. // * `rb_thread_add_event_hook`: // * `rb_thread_add_event_hook2`: //! * `rb_thread_alone`: [`Ruby::thread_alone`]. // * `rb_thread_atfork`: // * `rb_thread_atfork_before_exec`: // * `rb_thread_call_without_gvl`: // * `rb_thread_call_without_gvl2`: // * `rb_thread_call_with_gvl`: //! * `rb_thread_check_ints`: [`Ruby::thread_check_ints`]. //! * `rb_thread_create`: [`Ruby::thread_create`] & [`Ruby::thread_create_from_fn`]. //! * `rb_thread_current`: [`Ruby::thread_current`]. //! * `rb_thread_fd_close`: [`Ruby::thread_fd_close`]. // * `rb_thread_fd_select`: //! * `rb_thread_fd_writable`: [`Ruby::thread_fd_writable`]. //! * `rb_thread_interrupted`: [`Thread::interrupted`]. //! * `rb_thread_kill`: [`Thread::kill`]. //! * `rb_thread_local_aref`: [`Thread::local_aref`]. //! * `rb_thread_local_aset`: [`Thread::local_aset`]. //! * `rb_thread_main`: [`Ruby::thread_main`]. // * `rb_thread_remove_event_hook`: // * `rb_thread_remove_event_hook_with_data`: //! * `rb_thread_run`: [`Thread::run`]. //! * `rb_thread_schedule`: [`Ruby::thread_schedule`]. //! * `rb_thread_sleep`: See [`Ruby::thread_sleep`]. //! * `rb_thread_sleep_deadly`: [`Ruby::thread_sleep_deadly`]. //! * `rb_thread_sleep_forever`: [`Ruby::thread_sleep_forever`]. //! * `rb_thread_stop`: [`Ruby::thread_stop`]. //! * `rb_thread_wait_fd`: [`Ruby::thread_wait_fd`]. //! * `rb_thread_wait_for`: [`Ruby::thread_sleep`]. //! * `rb_thread_wakeup`: [`Thread::wakeup`]. //! * `rb_thread_wakeup_alive`: [`Thread::wakeup_alive`]. // * `rb_throw`: // * `rb_throw_obj`: // * `rb_timespec_now`: // * `rb_time_interval`: // * `rb_time_nano_new`: //! * `rb_time_new`: [`Ruby::time_new`]. // * `rb_time_num_new`: // * `rb_time_timespec`: // * `rb_time_timespec_interval`: // * `rb_time_timespec_new`: //! * `rb_time_timeval`: [`TryConvert`]. //! * `rb_time_utc_offset`: [`Time::utc_offset`]. // * `rb_tolower`: // * `rb_toupper`: //! * `rb_to_encoding`: [`TryConvert`] or [`Value::try_convert`]. //! * `rb_to_encoding_index`: [`TryConvert`] or [`Value::try_convert`]. //! * `rb_to_float`: [`TryConvert`] or [`Value::try_convert`]. // * `rb_to_id`: //! * `rb_to_int`: [`TryConvert`] or [`Value::try_convert`]. //! * `rb_to_symbol`: [`std::convert::From`]. // * `rb_tracearg_binding`: // * `rb_tracearg_callee_id`: // * `rb_tracearg_defined_class`: // * `rb_tracearg_event`: // * `rb_tracearg_event_flag`: // * `rb_tracearg_from_tracepoint`: // * `rb_tracearg_lineno`: // * `rb_tracearg_method_id`: // * `rb_tracearg_object`: // * `rb_tracearg_path`: // * `rb_tracearg_raised_exception`: // * `rb_tracearg_return_value`: // * `rb_tracearg_self`: // * `rb_tracepoint_disable`: // * `rb_tracepoint_enable`: // * `rb_tracepoint_enabled_p`: // * `rb_tracepoint_new`: // * `rb_trap_exit`: // * `rb_type`: // * `rb_typeddata_inherited_p`: // * `rb_typeddata_is_kind_of`: // * `RB_TYPE_P`: // * `rb_type_p`: //! //! ## `rb_u` // * `rb_uint2big`: // * `rb_uint2inum`: // * `RB_UINT2NUM`: // * `rb_uint2num_inline`: // * `rb_uint_new`: // * `rb_ull2inum`: // * `RB_ULL2NUM`: // * `rb_ull2num_inline`: // * `RB_ULONG2NUM`: // * `rb_ulong2num_inline`: // * `rb_undef`: // * `rb_undefine_finalizer`: //! * `rb_undef_alloc_func`: See [`Class::undef_default_alloc_func`]. // * `rb_undef_method`: // * `rb_unexpected_type`: // * `RB_UNLIKELY`: // * `rb_update_max_fd`: //! * `rb_usascii_encindex`: [`encoding::Index::usascii`]. //! * `rb_usascii_encoding`: //! [`RbEncoding::usascii`](encoding::RbEncoding::usascii). // * `rb_usascii_str_new`: // * `rb_usascii_str_new_cstr`: // * `rb_usascii_str_new_lit`: // * `rb_usascii_str_new_literal`: // * `rb_usascii_str_new_static`: //! * `rb_utf8_encindex`: [`encoding::Index::utf8`]. //! * `rb_utf8_encoding`: [`RbEncoding::utf8`](encoding::RbEncoding::utf8). //! * `rb_utf8_str_new`: [`RString::new`]. //! * `rb_utf8_str_new_cstr`: See [`RString::new`]. //! * `rb_utf8_str_new_lit`: Simmilar to [`r_string!`]. //! * `rb_utf8_str_new_literal`: Simmilar to [`r_string!`]. //! * `rb_utf8_str_new_static`: [`r_string!`]. // * `rb_uv_to_utf8`: //! //! ## `rb_v`-`rb_z` // * `rb_vrescue2`: // * `rb_vsprintf`: // * `rb_w32_fd_copy`: // * `rb_w32_fd_dup`: //! * `rb_waitpid`: [`Ruby::waitpid`]. // * `rb_warn`: //! * `rb_warning`: [`error::warning`]. // * `rb_write_error`: // * `rb_write_error2`: //! * `rb_yield`: [`block::yield_value`] / return [`block::Yield`]. // * `rb_yield_block`: //! * `rb_yield_splat`: [`block::yield_splat`] / return [`block::YieldSplat`]. // * `rb_yield_splat_kw`: //! * `rb_yield_values`: //! See [`block::yield_values`] / return [`block::YieldValues`]. //! * `rb_yield_values2`: //! See [`block::yield_values`] / return [`block::YieldValues`]. //! * `rb_yield_values_kw`: //! [`block::yield_values`] / return [`block::YieldValues`]. // * `RB_ZALLOC`: // * `RB_ZALLOC_N`: //! //! ## `rc`-`rt` // * `RCLASS`: // * `RCLASS_SUPER`: // * `RDATA`: // * `RETURN_ENUMERATOR`: // * `RETURN_ENUMERATOR_KW`: // * `RETURN_SIZED_ENUMERATOR`: // * `RETURN_SIZED_ENUMERATOR_KW`: // * `RFILE`: // * `RHASH_EMPTY_P`: // * `RHASH_SET_IFNONE`: // * `RHASH_SIZE`: // * `RHASH_TBL`: // * `RMATCH`: // * `RMATCH_REGS`: // * `RMODULE`: // * `ROBJECT`: // * `ROBJECT_IVPTR`: // * `ROBJECT_NUMIV`: // * `RREGEXP`: // * `RREGEXP_PTR`: // * `RREGEXP_SRC`: // * `RREGEXP_SRC_END`: // * `RREGEXP_SRC_LEN`: // * `RREGEXP_SRC_PTR`: //! * `RSTRING`: Similar to [`RString::from_value`]. //! * `RSTRING_EMBED_LEN`: Similar to [`RString::len`]. // * `RSTRING_END`: // * `RSTRING_GETMEM`: //! * `RSTRING_LEN`: [`RString::len`]. //! * `RSTRING_LENINT`: [`RString::len`]. //! * `RSTRING_PTR`: Similar to [`RString::as_str`] and [`RString::as_slice`]. // * `RSTRUCT_GET`: // * `RSTRUCT_LEN`: // * `RSTRUCT_SET`: //! * `RTEST`: [`Value::to_bool`] / [`TryConvert`] / [`Value::try_convert`]. // * `RTYPEDDATA`: // * `RTYPEDDATA_DATA`: // * `RTYPEDDATA_P`: // * `RTYPEDDATA_TYPE`: //! //! ## `ruby_` // * `RUBY_ALIGNAS`: // * `RUBY_ALIGNOF`: // * `RUBY_ASSERT`: // * `RUBY_ASSERT_ALWAYS`: // * `RUBY_ASSERT_FAIL`: // * `RUBY_ASSERT_MESG`: // * `RUBY_ASSERT_MESG_WHEN`: // * `RUBY_ASSERT_NDEBUG`: // * `RUBY_ASSERT_WHEN`: // * `RUBY_ATOMIC_ADD`: // * `RUBY_ATOMIC_CAS`: // * `RUBY_ATOMIC_DEC`: // * `RUBY_ATOMIC_EXCHANGE`: // * `RUBY_ATOMIC_FETCH_ADD`: // * `RUBY_ATOMIC_FETCH_SUB`: // * `RUBY_ATOMIC_INC`: // * `RUBY_ATOMIC_OR`: // * `RUBY_ATOMIC_PTR_CAS`: // * `RUBY_ATOMIC_PTR_EXCHANGE`: // * `RUBY_ATOMIC_SET`: // * `RUBY_ATOMIC_SIZE_ADD`: // * `RUBY_ATOMIC_SIZE_CAS`: // * `RUBY_ATOMIC_SIZE_DEC`: // * `RUBY_ATOMIC_SIZE_EXCHANGE`: // * `RUBY_ATOMIC_SIZE_INC`: // * `RUBY_ATOMIC_SIZE_SUB`: // * `RUBY_ATOMIC_SUB`: // * `RUBY_ATOMIC_VALUE_CAS`: // * `RUBY_ATOMIC_VALUE_EXCHANGE`: // * `ruby_brace_glob`: //! * `ruby_cleanup`: See [`embed::init`] and [`embed::Cleanup`]. // * `RUBY_DEBUG`: // * `ruby_debug`: // * `ruby_default_signal`: // * `ruby_each_words`: // * `ruby_enc_find_basename`: // * `ruby_enc_find_extname`: // * `ruby_executable_node`: // * `ruby_exec_node`: // * `ruby_finalize`: // * `ruby_getcwd`: // * `ruby_glob`: // * `ruby_incpush`: // * `ruby_init`: // * `ruby_init_loadpath`: // * `RUBY_INIT_STACK`: // * `ruby_init_stack`: // * `ruby_malloc_size_overflow`: // * `RUBY_METHOD_FUNC`: // * `ruby_native_thread_p`: // * `RUBY_NDEBUG`: // * `ruby_options`: // * `ruby_posix_signal`: // * `ruby_process_options`: // * `ruby_prog_init`: // * `ruby_qsort`: // * `ruby_run_node`: // * `ruby_scan_digits`: // * `ruby_scan_hex`: // * `ruby_scan_oct`: //! * `ruby_script`: Similar to [`embed::ruby_script`]. // * `ruby_setenv`: //! * `ruby_setup`: [`embed::setup`]. // * `ruby_set_argv`: //! * `ruby_set_script_name`: [`embed::ruby_script`]. // * `ruby_show_copyright`: // * `ruby_show_version`: // * `ruby_signal_name`: // * `ruby_sig_finalize`: // * `ruby_snprintf`: // * `ruby_stack_check`: // * `ruby_stack_length`: // * `ruby_stop`: // * `ruby_strdup`: // * `ruby_strtod`: // * `ruby_strtoul`: // * `ruby_sysinit`: // * `ruby_unsetenv`: // * `ruby_verbose`: // * `ruby_vm_at_exit`: // * `ruby_vm_destruct`: // * `ruby_vsnprintf`: // * `ruby_xcalloc`: // * `ruby_xfree`: // * `ruby_xmalloc`: // * `ruby_xmalloc2`: // * `ruby_xrealloc`: // * `ruby_xrealloc2`: //! //! ## S-Z // * `setproctitle`: // * `set_little_endian_p`: // * `set_native_size_p`: // * `StringValue`: // * `StringValueCStr`: // * `StringValuePtr`: // * `st_locale_insensitive_strcasecmp`: // * `st_locale_insensitive_strncasecmp`: //! * `TypedData_Get_Struct`: See [`wrap`] and [`TypedData`]. //! * `TypedData_Make_Struct`: See [`wrap`] and [`TypedData`]. //! * `TypedData_Wrap_Struct`: See [`wrap`] and [`TypedData`]. //!
#![cfg_attr(docsrs, feature(doc_cfg))] #![warn(missing_docs)] #![doc(test(attr(warn(unused))))] #![doc(test(attr(allow(unused_extern_crates))))] #[macro_use] mod macros; mod api; pub mod block; pub mod class; #[cfg(feature = "embed")] #[cfg_attr(docsrs, doc(cfg(feature = "embed")))] pub mod embed; pub mod encoding; mod enumerator; pub mod error; pub mod exception; #[cfg(any(ruby_gte_3_1, docsrs))] #[cfg_attr(docsrs, doc(cfg(ruby_gte_3_1)))] pub mod fiber; mod float; pub mod gc; mod integer; mod into_value; pub mod method; pub mod module; mod mutex; pub mod numeric; mod object; pub mod process; /// Traits that commonly should be in scope. pub mod prelude { pub use crate::{ class::Class as _, encoding::EncodingCapable as _, module::Module as _, numeric::Numeric as _, object::Object as _, try_convert::TryConvert as _, value::ReprValue as _, }; } pub mod r_array; mod r_bignum; mod r_complex; mod r_file; mod r_float; pub mod r_hash; mod r_match; mod r_object; mod r_rational; pub mod r_regexp; pub mod r_string; pub mod r_struct; mod r_typed_data; mod range; #[cfg(feature = "rb-sys")] #[cfg_attr(docsrs, doc(cfg(feature = "rb-sys")))] pub mod rb_sys; pub mod scan_args; pub mod symbol; mod thread; mod time; pub mod try_convert; pub mod typed_data; pub mod value; use std::{ffi::CString, mem::transmute, os::raw::c_int}; use ::rb_sys::{ rb_alias_variable, rb_backref_get, rb_call_super_kw, rb_current_receiver, rb_define_class, rb_define_global_const, rb_define_global_function, rb_define_module, rb_define_variable, rb_errinfo, rb_eval_string_protect, rb_require_string, rb_set_errinfo, VALUE, }; pub use magnus_macros::{init, wrap, DataTypeFunctions, TypedData}; #[cfg(any(ruby_gte_3_1, docsrs))] #[cfg_attr(docsrs, doc(cfg(ruby_gte_3_1)))] pub use crate::fiber::Fiber; #[cfg(ruby_use_flonum)] pub use crate::value::Flonum; pub use crate::{ api::Ruby, class::{Class, RClass}, enumerator::Enumerator, error::Error, exception::{Exception, ExceptionClass}, float::Float, integer::Integer, into_value::{ArgList, IntoValue, IntoValueFromNative, KwArgs, RArrayArgList}, module::{Attr, Module, RModule}, mutex::Mutex, numeric::Numeric, object::Object, r_array::RArray, r_bignum::RBignum, r_complex::RComplex, r_file::RFile, r_float::RFloat, r_hash::RHash, r_match::RMatch, r_object::RObject, r_rational::RRational, r_regexp::RRegexp, r_string::RString, r_struct::RStruct, r_typed_data::RTypedData, range::Range, symbol::Symbol, thread::Thread, time::Time, try_convert::TryConvert, typed_data::{DataType, DataTypeFunctions, TypedData}, value::{Fixnum, StaticSymbol, Value}, }; use crate::{ error::protect, method::Method, r_string::IntoRString, value::{private::ReprValue as _, IntoId, ReprValue}, }; /// Evaluate a literal string of Ruby code with the given local variables. /// /// Any type that implements [`IntoValue`] can be passed to Ruby. /// /// See also the [`eval`](fn@crate::eval) function. /// /// # Panics /// /// Panics if called from a non-Ruby thread. /// /// # Examples /// /// ``` /// # let _cleanup = unsafe { magnus::embed::init() }; /// let result: i64 = magnus::eval!("a + b", a = 1, b = 2).unwrap(); /// assert_eq!(result, 3) /// ``` /// ``` /// # let _cleanup = unsafe { magnus::embed::init() }; /// let a = 1; /// let b = 2; /// let result: i64 = magnus::eval!("a + b", a, b).unwrap(); /// assert_eq!(result, 3); /// ``` #[macro_export] macro_rules! eval { ($str:literal) => {{ $crate::eval!($crate::Ruby::get().unwrap(), $str) }}; ($str:literal, $($bindings:tt)*) => {{ $crate::eval!($crate::Ruby::get().unwrap(), $str, $($bindings)*) }}; ($ruby:expr, $str:literal) => {{ use $crate::{r_string::IntoRString, value::ReprValue}; $ruby .eval::<$crate::Value>("binding") .unwrap() .funcall("eval", ($str.into_r_string_with(&$ruby),)) }}; ($ruby:expr, $str:literal, $($bindings:tt)*) => {{ use $crate::{r_string::IntoRString, value::ReprValue}; let binding = $ruby.eval::<$crate::Value>("binding").unwrap(); $crate::bind!(binding, $($bindings)*); binding.funcall("eval", ($str.into_r_string_with(&$ruby),)) }}; } #[doc(hidden)] #[macro_export] macro_rules! bind { ($binding:ident,) => {}; ($binding:ident, $k:ident = $v:expr) => {{ use $crate::symbol::IntoSymbol; let _: $crate::Value = $binding.funcall( "local_variable_set", (stringify!($k).into_symbol_with(&$crate::Ruby::get_with($binding)), $v), ) .unwrap(); }}; ($binding:ident, $k:ident) => {{ use $crate::symbol::IntoSymbol; let _: $crate::Value = $binding.funcall( "local_variable_set", (stringify!($k).into_symbol_with(&$crate::Ruby::get_with($binding)), $k), ) .unwrap(); }}; ($binding:ident, $k:ident = $v:expr, $($rest:tt)*) => {{ use $crate::symbol::IntoSymbol; let _: $crate::Value = $binding.funcall( "local_variable_set", (stringify!($k).into_symbol_with(&$crate::Ruby::get_with($binding)), $v), ) .unwrap(); $crate::bind!($binding, $($rest)*); }}; ($binding:ident, $k:ident, $($rest:tt)*) => {{ use $crate::symbol::IntoSymbol; let _: $crate::Value = $binding.funcall( "local_variable_set", (stringify!($k).into_symbol_with(&$crate::Ruby::get_with($binding)), $k), ) .unwrap(); $crate::bind!($binding, $($rest)*); }}; } /// Asserts a Ruby expression evaluates to a truthy value. /// /// This macro uses the Ruby /// [`power_assert`](https://github.com/ruby/power_assert) gem that is part of /// the Ruby standard library to generate detailed failure messages. /// /// ``` should_panic /// # let _cleanup = unsafe { magnus::embed::init() }; /// magnus::rb_assert!("1 + 2 == 4"); /// ``` /// /// Outputs: /// /// ``` plain /// thread 'main' panicked at ' /// 1 + 2 == 4 /// | | /// | false /// 3', src/lib.rs:5:1 /// ``` /// /// # Panics /// /// Panics if called from a non-Ruby thread. /// /// # Examples /// /// ``` /// # let _cleanup = unsafe { magnus::embed::init() }; /// magnus::rb_assert!("1 + 2 == 3"); /// ``` /// /// Passing [`Ruby`] to avoid the Ruby thread check: /// /// ``` /// # let _cleanup = unsafe { magnus::embed::init() }; /// let ruby = magnus::Ruby::get().unwrap(); /// magnus::rb_assert!(ruby, "1 + 2 == 3"); /// ``` /// /// Making local variables accessible to Ruby: /// /// ``` /// # let _cleanup = unsafe { magnus::embed::init() }; /// let a = 1; /// let b = 2; /// magnus::rb_assert!("a + b == 3", a, b); /// ``` /// /// Directly setting local variables: /// /// ``` /// # let _cleanup = unsafe { magnus::embed::init() }; /// magnus::rb_assert!("a + b == 3", a = 1, b = 2); /// ``` #[macro_export] macro_rules! rb_assert { ($expr:literal) => {{ $crate::rb_assert!($crate::Ruby::get().unwrap(), $expr) }}; ($expr:literal, $($bindings:tt)*) => {{ $crate::rb_assert!($crate::Ruby::get().unwrap(), $expr, $($bindings)*) }}; ($ruby:expr, $expr:literal) => {{ $crate::rb_assert!($ruby, $expr,) }}; ($ruby:expr, $expr:literal, $($bindings:tt)*) => {{ let msg: Option = $crate::eval!($ruby, r#" require "power_assert" PowerAssert.start(__assert_exp__, source_binding: binding) do |pa| "\n#{pa.message}" unless pa.yield end "#, __assert_exp__ = $expr, $($bindings)*).unwrap(); if let Some(msg) = msg { panic!("{}", msg) }; }}; } /// # Globals /// /// Functions for defining global variables, constants, etc, as well as /// accessing current Ruby execution status such as calling the current `super` /// method. /// /// See also [functions in the root module](self#functions). impl Ruby { /// Define a class in the root scope. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_class("Example", ruby.class_object())?; /// rb_assert!(ruby, "Example.is_a?(Class)"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn define_class(&self, name: &str, superclass: RClass) -> Result { debug_assert_value!(superclass); let name = CString::new(name).unwrap(); let superclass = superclass.as_rb_value(); protect(|| unsafe { RClass::from_rb_value_unchecked(rb_define_class(name.as_ptr(), superclass)) }) } /// Define a module in the root scope. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_module("Example")?; /// rb_assert!(ruby, "Example.is_a?(Module)"); /// rb_assert!(ruby, "!Example.is_a?(Class)"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn define_module(&self, name: &str) -> Result { let name = CString::new(name).unwrap(); protect(|| unsafe { RModule::from_rb_value_unchecked(rb_define_module(name.as_ptr())) }) } /// Define an exception class in the root scope. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_error("ExampleError", ruby.exception_standard_error())?; /// rb_assert!(ruby, "ExampleError.is_a?(Class)"); /// rb_assert!(ruby, "ExampleError < Exception"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn define_error( &self, name: &str, superclass: ExceptionClass, ) -> Result { self.define_class(name, superclass.as_r_class()) .map(|c| unsafe { ExceptionClass::from_value_unchecked(c.as_value()) }) } /// Define a global variable. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let v = ruby.define_variable("example", 42)?; /// rb_assert!(ruby, "$example == 42"); /// /// // safe as long as another thread isn't modifying v /// unsafe { /// *v = ruby.str_new("answer").as_value(); /// } /// rb_assert!(ruby, r#"$example == "answer""#); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn define_variable(&self, name: &str, initial: T) -> Result<*mut Value, Error> where T: IntoValue, { let initial = self.into_value(initial); debug_assert_value!(initial); let name = CString::new(name).unwrap(); let ptr = Box::into_raw(Box::new(initial)); unsafe { rb_define_variable(name.as_ptr(), ptr as *mut VALUE); } Ok(ptr) } /// Alias the global variable `src` as `dst`. /// /// Unlike [`define_variable`](Ruby::define_variable), the preceeding `$` /// of the global variable's name is required, otherwise the alias will not /// be accessable from Ruby. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_variable("example", 42)?; /// ruby.alias_variable("$answer", "$example")?; /// rb_assert!(ruby, "$answer == 42"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn alias_variable(&self, dst: T, src: U) -> Result<(), Error> where T: IntoId, U: IntoId, { let dst = dst.into_id_with(self); let src = src.into_id_with(self); protect(|| { unsafe { rb_alias_variable(dst.as_rb_id(), src.as_rb_id()) }; self.qnil() })?; Ok(()) } /// Define a global constant. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_const("EXAMPLE", 42)?; /// rb_assert!(ruby, "EXAMPLE == 42"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn define_global_const(&self, name: &str, value: T) -> Result<(), Error> where T: IntoValue, { let value = self.into_value(value); let name = CString::new(name).unwrap(); protect(|| { unsafe { rb_define_global_const(name.as_ptr(), value.as_rb_value()); } self.qnil() })?; Ok(()) } /// Define a method in the root scope. /// /// # Examples /// /// ``` /// use magnus::{function, rb_assert, Error, Ruby}; /// /// fn greet(subject: String) -> String { /// format!("Hello, {}!", subject) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_function("greet", function!(greet, 1)); /// rb_assert!(ruby, r#"greet("world") == "Hello, world!""#); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn define_global_function(&self, name: &str, func: M) where M: Method, { let name = CString::new(name).unwrap(); unsafe { rb_define_global_function(name.as_ptr(), transmute(func.as_ptr()), M::arity().into()); } } /// Returns the result of the most recent regexp match. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let regexp = ruby.reg_new("b(.)r", Default::default())?; /// let result = regexp.reg_match("foo bar baz")?; /// assert_eq!(result, Some(4)); /// /// let match_data = ruby.backref_get().unwrap(); /// assert_eq!(match_data.matched().to_string()?, String::from("bar")); /// assert_eq!( /// match_data.nth_match(1).map(|v| v.to_string().unwrap()), /// Some(String::from("a")) /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn backref_get(&self) -> Option { unsafe { let value = Value::new(rb_backref_get()); (!value.is_nil()).then(|| RMatch::from_rb_value_unchecked(value.as_rb_value())) } } /// Return the Ruby `self` of the current method context. /// /// Returns `Err` if called outside a method context or the conversion /// fails. /// /// # Examples /// /// ``` /// use magnus::{method, prelude::*, rb_assert, Error, Ruby, Value}; /// /// fn test(ruby: &Ruby, rb_self: Value) -> Result { /// rb_self.equal(ruby.current_receiver::()?) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_function("test", method!(test, 0)); /// /// rb_assert!(ruby, "test"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn current_receiver(&self) -> Result where T: TryConvert, { protect(|| unsafe { Value::new(rb_current_receiver()) }).and_then(TryConvert::try_convert) } /// Call the super method of the current method context. /// /// Returns `Ok(T)` if the super method exists and returns without error, /// and the return value converts to a `T`, or returns `Err` if there is no /// super method, the super method raises or the conversion fails. /// /// # Examples /// /// ``` /// use magnus::{function, prelude::*, rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = ruby.eval( /// r#" /// class A /// def test /// "Hello from A" /// end /// end /// A /// "#, /// )?; /// /// let b = ruby.define_class("B", a)?; /// fn test(ruby: &Ruby) -> Result { /// let s: String = ruby.call_super(())?; /// Ok(format!("{}, and hello from B", s)) /// } /// b.define_method("test", function!(test, 0))?; /// /// rb_assert!(ruby, r#"B.new.test == "Hello from A, and hello from B""#); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn call_super(&self, args: A) -> Result where A: ArgList, T: TryConvert, { unsafe { let kw_splat = into_value::kw_splat(&args); let args = args.into_arg_list_with(self); let slice = args.as_ref(); protect(|| { Value::new(rb_call_super_kw( slice.len() as c_int, slice.as_ptr() as *const VALUE, kw_splat as c_int, )) }) .and_then(TryConvert::try_convert) } } /// Finds and loads the given feature if not already loaded. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ruby.require("net/http")?); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn require(&self, feature: T) -> Result where T: IntoRString, { let feature = feature.into_r_string_with(self); protect(|| unsafe { Value::new(rb_require_string(feature.as_rb_value())) }) .and_then(TryConvert::try_convert) } /// Evaluate a string of Ruby code, converting the result to a `T`. /// /// Ruby will use the 'ASCII-8BIT' (aka binary) encoding for any Ruby /// string literals in the passed string of Ruby code. See the /// [`eval`](macro@crate::eval) macro for an alternative that supports /// utf-8. /// /// Errors if `s` contains a null byte, the conversion fails, or on an /// uncaught Ruby exception. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert_eq!(ruby.eval::("1 + 2")?, 3); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn eval(&self, s: &str) -> Result where T: TryConvert, { let mut state = 0; // safe ffi to Ruby, captures raised errors (+ brake, throw, etc) as state let result = unsafe { let s = CString::new(s) .map_err(|e| Error::new(self.exception_script_error(), e.to_string()))?; rb_eval_string_protect(s.as_c_str().as_ptr(), &mut state as *mut _) }; match state { // Tag::None 0 => T::try_convert(Value::new(result)), // Tag::Raise 6 => unsafe { let ex = Exception::from_rb_value_unchecked(rb_errinfo()); rb_set_errinfo(self.qnil().as_rb_value()); Err(ex.into()) }, other => Err(Error::from_tag(unsafe { transmute(other) })), } } } /// Define a class in the root scope. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::define_class`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{class, define_class, rb_assert}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// define_class("Example", class::object()).unwrap(); /// rb_assert!("Example.is_a?(Class)"); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::define_class` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn define_class(name: &str, superclass: RClass) -> Result { get_ruby!().define_class(name, superclass) } /// Define a module in the root scope. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::define_module`] for /// the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{define_module, rb_assert}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// define_module("Example").unwrap(); /// rb_assert!("Example.is_a?(Module)"); /// rb_assert!("!Example.is_a?(Class)"); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::define_module` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn define_module(name: &str) -> Result { get_ruby!().define_module(name) } /// Define an exception class in the root scope. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::define_error`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{define_error, exception, rb_assert}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// define_error("ExampleError", exception::standard_error()).unwrap(); /// rb_assert!("ExampleError.is_a?(Class)"); /// rb_assert!("ExampleError < Exception"); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::define_error` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn define_error(name: &str, superclass: ExceptionClass) -> Result { get_ruby!().define_error(name, superclass) } /// Define a global variable. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::define_variable`] for /// the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{define_variable, prelude::*, rb_assert, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let v = define_variable("example", 42).unwrap(); /// rb_assert!("$example == 42"); /// /// // safe as long as another thread isn't modifying v /// unsafe { /// *v = RString::new("answer").as_value(); /// } /// rb_assert!(r#"$example == "answer""#); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::define_variable` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn define_variable(name: &str, initial: T) -> Result<*mut Value, Error> where T: IntoValue, { get_ruby!().define_variable(name, initial) } /// Define a global constant. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::define_global_const`] /// for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{define_global_const, rb_assert}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// define_global_const("EXAMPLE", 42).unwrap(); /// rb_assert!("EXAMPLE == 42"); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::define_global_const` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn define_global_const(name: &str, value: T) -> Result<(), Error> where T: IntoValue, { get_ruby!().define_global_const(name, value) } /// Define a method in the root scope. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::define_global_function`] for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{define_global_function, function, rb_assert}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// fn greet(subject: String) -> String { /// format!("Hello, {}!", subject) /// } /// /// define_global_function("greet", function!(greet, 1)); /// rb_assert!(r#"greet("world") == "Hello, world!""#); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::define_global_function` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn define_global_function(name: &str, func: M) where M: Method, { get_ruby!().define_global_function(name, func) } /// Returns the result of the most recent regexp match. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::backref_get`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{backref_get, RRegexp}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let regexp = RRegexp::new("b(.)r", Default::default()).unwrap(); /// let result = regexp.reg_match("foo bar baz").unwrap(); /// assert_eq!(result, Some(4)); /// /// let match_data = backref_get().unwrap(); /// assert_eq!( /// match_data.matched().to_string().unwrap(), /// String::from("bar") /// ); /// assert_eq!( /// match_data.nth_match(1).map(|v| v.to_string().unwrap()), /// Some(String::from("a")) /// ); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::backref_get` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn backref_get() -> Option { get_ruby!().backref_get() } /// Return the Ruby `self` of the current method context. /// /// Returns `Err` if called outside a method context or the conversion fails. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::current_receiver`] for /// the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{ /// current_receiver, define_global_function, method, prelude::*, rb_assert, Error, Value, /// }; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// fn example(rb_self: Value) -> Result { /// rb_self.equal(current_receiver::()?) /// } /// define_global_function("example", method!(example, 0)); /// /// rb_assert!("example"); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::current_receiver` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn current_receiver() -> Result where T: TryConvert, { get_ruby!().current_receiver() } /// Call the super method of the current method context. /// /// Returns `Ok(T)` if the super method exists and returns without error, and /// the return value converts to a `T`, or returns `Err` if there is no super /// method, the super method raises or the conversion fails. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::call_super`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{call_super, define_class, eval, function, prelude::*, rb_assert, Error}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let a = eval( /// r#" /// class A /// def example /// "Hello from A" /// end /// end /// A /// "#, /// ) /// .unwrap(); /// /// let b = define_class("B", a).unwrap(); /// fn example() -> Result { /// let s: String = call_super(())?; /// Ok(format!("{}, and hello from B", s)) /// } /// b.define_method("example", function!(example, 0)).unwrap(); /// /// rb_assert!(r#"B.new.example == "Hello from A, and hello from B""#) /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::call_super` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn call_super(args: A) -> Result where A: ArgList, T: TryConvert, { get_ruby!().call_super(args) } /// Finds and loads the given feature if not already loaded. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::require`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// # let _cleanup = unsafe { magnus::embed::init() }; /// use magnus::require; /// /// assert!(require("net/http").unwrap()); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::require` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn require(feature: T) -> Result where T: IntoRString, { get_ruby!().require(feature) } /// Evaluate a string of Ruby code, converting the result to a `T`. /// /// Ruby will use the 'ASCII-8BIT' (aka binary) encoding for any Ruby string /// literals in the passed string of Ruby code. See the /// [`eval`](macro@crate::eval) macro for an alternative that supports utf-8. /// /// Errors if `s` contains a null byte, the conversion fails, or on an uncaught /// Ruby exception. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::eval`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # let _cleanup = unsafe { magnus::embed::init() }; /// assert_eq!(magnus::eval::("1 + 2").unwrap(), 3); /// ``` #[inline] pub fn eval(s: &str) -> Result where T: TryConvert, { get_ruby!().eval(s) } magnus-0.7.1/src/macros.rs000064400000000000000000000032771046102023000135550ustar 00000000000000/// Debug assertation that the Value hasn't been garbage collected. // This isn't infallible, if the original object was gc'd and that slot // reused already this won't panic like it should, but we're trying our // best here. macro_rules! debug_assert_value { ($value:expr) => { // The memory this points to is managed by Ruby's GC and we can't // really know if it's safe to access as with GC compaction this may // point to memory now outside that owned by the process. We will likly // segfault in that case, which is kind of OK, as we're trying to panic // anyway. #[allow(unused_unsafe)] #[cfg(debug_assertions)] match unsafe { $crate::value::private::ReprValue::rb_type($value) } { ::rb_sys::ruby_value_type::RUBY_T_NONE | ::rb_sys::ruby_value_type::RUBY_T_ZOMBIE => { panic!("Attempting to access garbage collected Object") } ::rb_sys::ruby_value_type::RUBY_T_MOVED => { panic!("Attempting to access garbage collected Object") } _ => (), }; }; } /// Get a `Ruby`. /// /// Panics in debug mode if not on a Ruby thread. /// Undefined behaviour in release if not on a Ruby thread. /// /// The check is currently not performed in release mode as the Ruby API /// hasn't been finalised and there isn't an alternative to this that avoids /// the check and the associated perfomance hit. macro_rules! get_ruby { () => { if cfg!(debug_assertions) { $crate::Ruby::get().unwrap() } else { #[allow(unused_unsafe)] unsafe { $crate::Ruby::get_unchecked() } } }; } magnus-0.7.1/src/method.rs000064400000000000000000001767201046102023000135550ustar 00000000000000//! Traits for exposing Rust functions as Ruby methods. #![allow(clippy::too_many_arguments)] #![allow(clippy::many_single_char_names)] #![allow(clippy::missing_safety_doc)] use std::{ffi::c_void, os::raw::c_int, panic::AssertUnwindSafe, slice}; use seq_macro::seq; use crate::{ block::{ do_yield_iter, do_yield_splat_iter, do_yield_values_iter, Proc, Yield, YieldSplat, YieldValues, }, error::{raise, Error, IntoError}, into_value::{ArgList, IntoValue}, r_array::RArray, try_convert::TryConvert, value::{ReprValue, Value}, Ruby, }; mod private { use super::*; pub unsafe trait Method { fn arity() -> i8; #[allow(clippy::wrong_self_convention)] fn as_ptr(self) -> *mut c_void; } unsafe impl Method for unsafe extern "C" fn(Value, RArray) -> Value { fn arity() -> i8 { -2 } fn as_ptr(self) -> *mut c_void { self as *mut c_void } } unsafe impl Method for unsafe extern "C" fn(c_int, *const Value, Value) -> Value { fn arity() -> i8 { -1 } fn as_ptr(self) -> *mut c_void { self as *mut c_void } } macro_rules! impl_method { ($n:literal) => { seq!(_ in 0..=$n { unsafe impl Method for unsafe extern "C" fn(#(Value,)*) -> Value { fn arity() -> i8 { $n } fn as_ptr(self) -> *mut c_void { self as *mut c_void } } }); } } seq!(N in 0..=16 { impl_method!(N); }); pub trait ReturnValue { fn into_return_value(self) -> Result; } impl ReturnValue for Result where T: IntoValue, E: IntoError, { fn into_return_value(self) -> Result { let ruby = unsafe { Ruby::get_unchecked() }; self.map(|val| val.into_value_with(&ruby)) .map_err(|err| err.into_error(&ruby)) } } impl ReturnValue for T where T: IntoValue, { fn into_return_value(self) -> Result { Ok::(self).into_return_value() } } impl ReturnValue for Result, E> where I: Iterator, T: IntoValue, E: IntoError, { fn into_return_value(self) -> Result { let ruby = unsafe { Ruby::get_unchecked() }; self.map(|i| match i { Yield::Iter(iter) => unsafe { do_yield_iter(iter); ruby.qnil().as_value() }, Yield::Enumerator(e) => e.into_value_with(&ruby), }) .map_err(|err| err.into_error(&ruby)) } } impl ReturnValue for Yield where I: Iterator, T: IntoValue, { fn into_return_value(self) -> Result { Ok::(self).into_return_value() } } impl ReturnValue for Result, E> where I: Iterator, T: ArgList, E: IntoError, { fn into_return_value(self) -> Result { let ruby = unsafe { Ruby::get_unchecked() }; self.map(|i| match i { YieldValues::Iter(iter) => unsafe { do_yield_values_iter(iter); ruby.qnil().as_value() }, YieldValues::Enumerator(e) => e.into_value_with(&ruby), }) .map_err(|err| err.into_error(&ruby)) } } impl ReturnValue for YieldValues where I: Iterator, T: ArgList, { fn into_return_value(self) -> Result { Ok::(self).into_return_value() } } impl ReturnValue for Result, E> where I: Iterator, E: IntoError, { fn into_return_value(self) -> Result { let ruby = unsafe { Ruby::get_unchecked() }; self.map(|i| match i { YieldSplat::Iter(iter) => unsafe { do_yield_splat_iter(iter); ruby.qnil().as_value() }, YieldSplat::Enumerator(e) => e.into_value_with(&ruby), }) .map_err(|err| err.into_error(&ruby)) } } impl ReturnValue for YieldSplat where I: Iterator, { fn into_return_value(self) -> Result { Ok::(self).into_return_value() } } pub trait InitReturn { fn into_init_return(self) -> Result<(), Error>; } impl InitReturn for () { fn into_init_return(self) -> Result<(), Error> { Ok(()) } } impl InitReturn for Result<(), E> where E: IntoError, { fn into_init_return(self) -> Result<(), Error> { self.map_err(|err| err.into_error(&unsafe { Ruby::get_unchecked() })) } } pub trait BlockReturn { fn into_block_return(self) -> Result; } impl BlockReturn for Result where T: IntoValue, { fn into_block_return(self) -> Result { self.map(|val| unsafe { val.into_value_unchecked() }) } } impl BlockReturn for T where T: IntoValue, { fn into_block_return(self) -> Result { Ok(self).into_block_return() } } } /// Trait implemented for function pointers that can be registed as Ruby /// methods. /// /// While it is possible to directly write functions that will automatically /// implement this trait it is not recommended, as those functions will not /// have the type conversions or error handling usually provided by this /// library. See the [`method`](crate::method!) and /// [`function`](crate::function!) macros for converting functions to an /// implementor of this trait. /// /// This trait is implemented for the following function signatures: /// /// | Arity | Signature | /// |-------|--------------------------------------------------------------| /// | -2 | `unsafe extern "C" fn(Value, RArray) -> Value;` | /// | -1 | `unsafe extern "C" fn(c_int, *const Value, Value) -> Value;` | /// | 0 | `unsafe extern "C" fn(Value) -> Value;` | /// | 1 | `unsafe extern "C" fn(Value, Value) -> Value;` | /// | 2 | `unsafe extern "C" fn(Value, Value, Value) -> Value;` | /// | ... | ... | /// | 16 | ... | /// /// note: for arity 0..=16 the number of arguments is 1 greater than the arity, /// due to the initial `self` argument. pub trait Method: private::Method {} impl Method for T where T: private::Method {} /// Trait marking types that can be returned to Ruby. /// /// Implemented for the following types: /// /// * `T` /// * [`Yield`] /// * [`YieldValues`] /// * [`YieldSplat`] /// * `Result` /// * `Result, magnus::Error>` /// * `Result, magnus::Error>` /// * `Result, magnus::Error>` /// /// where `I` implements `Iterator` and `T` implements [`IntoValue`]. /// /// When is `Err(magnus::Error)` returned to Ruby it will be conveted to and /// raised as a Ruby exception. /// /// [`Yield`], [`YieldValues`], and [`YieldSplat`] allow returning a Rust /// [`Iterator`] to be bridged to Ruby method that calls a block with the /// elements of that [`Iterator`]. /// /// Note: functions without a specified return value will return `()`. `()` /// implements [`IntoValue`] (converting to `nil`). pub trait ReturnValue: private::ReturnValue {} impl ReturnValue for T where T: private::ReturnValue {} /// Trait marking types that can be returned to Ruby from a library /// [`init`](magnus_macros::init) function. /// /// Implemented for the following types: /// /// * `()` /// * `Result<(), magnus::Error>` /// /// When is `Err(magnus::Error)` returned to Ruby it will be conveted to and /// raised as a Ruby exception. /// /// Note: functions without a specified return value will return `()`. `()` /// implements [`IntoValue`] (converting to `nil`). pub trait InitReturn: private::InitReturn {} impl InitReturn for T where T: private::InitReturn {} /// Trait marking types that can be returned to Ruby from a block. /// /// Implemented for the following types: /// /// * `T` /// * `Result` /// /// where `T` implements [`IntoValue`]. /// /// When is `Err(magnus::Error)` returned to Ruby it will be conveted to and /// raised as a Ruby exception. /// /// Note: functions without a specified return value will return `()`. `()` /// implements [`IntoValue`] (converting to `nil`). pub trait BlockReturn: private::BlockReturn {} impl BlockReturn for T where T: private::BlockReturn {} /// Helper trait for wrapping a function with type conversions and error /// handling, as an 'init' function. /// /// See the [`init`](magnus_macros::init) macro. #[doc(hidden)] pub trait Init where Self: Sized + Fn() -> Res, Res: InitReturn, { #[inline] unsafe fn call_handle_error(self) { let res = match std::panic::catch_unwind(AssertUnwindSafe(|| (self)().into_init_return())) { Ok(v) => v, Err(e) => Err(Error::from_panic(e)), }; match res { Ok(v) => v, Err(e) => raise(e), } } } impl Init for Func where Func: Fn() -> Res, Res: InitReturn, { } /// Helper trait for wrapping a function with type conversions and error /// handling, as an 'init' function being passed [`&Ruby`](Ruby). /// /// See the [`init`](magnus_macros::init) macro. #[doc(hidden)] pub trait RubyInit where Self: Sized + Fn(&Ruby) -> Res, Res: InitReturn, { #[inline] unsafe fn call_handle_error(self) { let res = match std::panic::catch_unwind(AssertUnwindSafe(|| { (self)(&Ruby::get_unchecked()).into_init_return() })) { Ok(v) => v, Err(e) => Err(Error::from_panic(e)), }; match res { Ok(v) => v, Err(e) => raise(e), } } } impl RubyInit for Func where Func: Fn(&Ruby) -> Res, Res: InitReturn, { } /// Helper trait for wrapping a function with type conversions and error /// handling, as an 'block' function. /// /// See the [`Value::block_call`] function. #[doc(hidden)] pub trait Block where Self: Sized + FnOnce(&Ruby, &[Value], Option) -> Res, Res: BlockReturn, { #[inline] unsafe fn call_convert_value( self, argc: c_int, argv: *const Value, blockarg: Value, ) -> Result { let ruby = Ruby::get_unchecked(); let args = slice::from_raw_parts(argv, argc as usize); (self)(&ruby, args, Proc::from_value(blockarg)).into_block_return() } #[inline] unsafe fn call_handle_error(self, argc: c_int, argv: *const Value, blockarg: Value) -> Value { let res = match std::panic::catch_unwind(AssertUnwindSafe(|| { self.call_convert_value(argc, argv, blockarg) })) { Ok(v) => v, Err(e) => Err(Error::from_panic(e)), }; match res { Ok(v) => v, Err(e) => raise(e), } } } impl Block for Func where Func: FnOnce(&Ruby, &[Value], Option) -> Res, Res: BlockReturn, { } /// Helper trait for wrapping a function with type conversions and error /// handling, when creating a thread. /// /// See the [`Ruby::thread_create`] function. #[doc(hidden)] pub trait Thread where Self: Sized + FnOnce(&Ruby) -> Res, Res: BlockReturn, { #[inline] unsafe fn call_convert_value(self) -> Result { (self)(&Ruby::get_unchecked()).into_block_return() } #[inline] unsafe fn call_handle_error(self) -> Value { let res = match std::panic::catch_unwind(AssertUnwindSafe(|| self.call_convert_value())) { Ok(v) => v, Err(e) => Err(Error::from_panic(e)), }; match res { Ok(v) => v, Err(e) => raise(e), } } } impl Thread for Func where Func: FnOnce(&Ruby) -> Res, Res: BlockReturn, { } /// Helper trait for wrapping a function with type conversions and error /// handling, when calling [`Mutex::synchronize`](crate::Mutext::synchronize). #[doc(hidden)] pub trait Synchronize where Self: Sized + FnOnce() -> Res, Res: BlockReturn, { #[inline] unsafe fn call_convert_value(self) -> Result { (self)().into_block_return() } #[inline] unsafe fn call_handle_error(self) -> Value { let res = match std::panic::catch_unwind(AssertUnwindSafe(|| self.call_convert_value())) { Ok(v) => v, Err(e) => Err(Error::from_panic(e)), }; match res { Ok(v) => v, Err(e) => raise(e), } } } impl Synchronize for Func where Func: FnOnce() -> Res, Res: BlockReturn, { } /// Helper trait for wrapping a function as a Ruby method taking self and a /// Ruby array of arguments, with type conversions and error handling. /// /// See the [`method`](crate::method!) macro. #[doc(hidden)] pub trait MethodRbAry where Self: Sized + Fn(RbSelf, Args) -> Res, RbSelf: TryConvert, Args: TryConvert, Res: ReturnValue, { #[inline] fn call_convert_value(self, rb_self: Value, args: RArray) -> Result { (self)( TryConvert::try_convert(rb_self)?, TryConvert::try_convert(args.as_value())?, ) .into_return_value() } #[inline] unsafe fn call_handle_error(self, rb_self: Value, args: RArray) -> Value { let res = match std::panic::catch_unwind(AssertUnwindSafe(|| { self.call_convert_value(rb_self, args) })) { Ok(v) => v, Err(e) => Err(Error::from_panic(e)), }; match res { Ok(v) => v, Err(e) => raise(e), } } } impl MethodRbAry for Func where Func: Fn(RbSelf, Args) -> Res, RbSelf: TryConvert, Args: TryConvert, Res: ReturnValue, { } /// Helper trait for wrapping a function as a Ruby method taking /// [`&Ruby`](Ruby), self, and a Ruby array of arguments, with type conversions /// and error handling. /// /// See the [`method`](crate::method!) macro. #[doc(hidden)] pub trait RubyMethodRbAry where Self: Sized + Fn(&Ruby, RbSelf, Args) -> Res, RbSelf: TryConvert, Args: TryConvert, Res: ReturnValue, { #[inline] fn call_convert_value(self, rb_self: Value, args: RArray) -> Result { (self)( &Ruby::get_with(rb_self), TryConvert::try_convert(rb_self)?, TryConvert::try_convert(args.as_value())?, ) .into_return_value() } #[inline] unsafe fn call_handle_error(self, rb_self: Value, args: RArray) -> Value { let res = match std::panic::catch_unwind(AssertUnwindSafe(|| { self.call_convert_value(rb_self, args) })) { Ok(v) => v, Err(e) => Err(Error::from_panic(e)), }; match res { Ok(v) => v, Err(e) => raise(e), } } } impl RubyMethodRbAry for Func where Func: Fn(&Ruby, RbSelf, Args) -> Res, RbSelf: TryConvert, Args: TryConvert, Res: ReturnValue, { } /// Helper trait for wrapping a function as a Ruby method taking self and a /// slice of arguments, with type conversions and error handling. /// /// See the [`method`](crate::method!) macro. #[doc(hidden)] pub trait MethodCAry where Self: Sized + Fn(RbSelf, &[Value]) -> Res, RbSelf: TryConvert, Res: ReturnValue, { #[inline] unsafe fn call_convert_value( self, argc: c_int, argv: *const Value, rb_self: Value, ) -> Result { let args = slice::from_raw_parts(argv, argc as usize); (self)(TryConvert::try_convert(rb_self)?, args).into_return_value() } #[inline] unsafe fn call_handle_error(self, argc: c_int, argv: *const Value, rb_self: Value) -> Value { let res = match std::panic::catch_unwind(AssertUnwindSafe(|| { self.call_convert_value(argc, argv, rb_self) })) { Ok(v) => v, Err(e) => Err(Error::from_panic(e)), }; match res { Ok(v) => v, Err(e) => raise(e), } } } impl MethodCAry for Func where Func: Fn(RbSelf, &[Value]) -> Res, RbSelf: TryConvert, Res: ReturnValue, { } /// Helper trait for wrapping a function as a Ruby method taking /// [`&Ruby`](Ruby), self, and a slice of arguments, with type conversions and /// error handling. /// /// See the [`method`](crate::method!) macro. #[doc(hidden)] pub trait RubyMethodCAry where Self: Sized + Fn(&Ruby, RbSelf, &[Value]) -> Res, RbSelf: TryConvert, Res: ReturnValue, { #[inline] unsafe fn call_convert_value( self, argc: c_int, argv: *const Value, rb_self: Value, ) -> Result { let args = slice::from_raw_parts(argv, argc as usize); (self)( &Ruby::get_with(rb_self), TryConvert::try_convert(rb_self)?, args, ) .into_return_value() } #[inline] unsafe fn call_handle_error(self, argc: c_int, argv: *const Value, rb_self: Value) -> Value { let res = match std::panic::catch_unwind(AssertUnwindSafe(|| { self.call_convert_value(argc, argv, rb_self) })) { Ok(v) => v, Err(e) => Err(Error::from_panic(e)), }; match res { Ok(v) => v, Err(e) => raise(e), } } } impl RubyMethodCAry for Func where Func: Fn(&Ruby, RbSelf, &[Value]) -> Res, RbSelf: TryConvert, Res: ReturnValue, { } macro_rules! method_n { ($name:ident, $ruby_name:ident, $n:literal) => { seq!(N in 0..$n { /// Helper trait for wrapping a function as a Ruby method taking /// self and N arguments, with type conversions and error handling. /// /// See the [`method`](crate::method!) macro. #[doc(hidden)] pub trait $name where Self: Sized + Fn(RbSelf, #(T~N,)*) -> Res, RbSelf: TryConvert, #(T~N: TryConvert,)* Res: ReturnValue, { #[inline] fn call_convert_value(self, rb_self: Value, #(arg~N: Value,)*) -> Result { (self)( TryConvert::try_convert(rb_self)?, #(TryConvert::try_convert(arg~N)?,)* ).into_return_value() } #[inline] unsafe fn call_handle_error(self, rb_self: Value, #(arg~N: Value,)*) -> Value { let res = match std::panic::catch_unwind(AssertUnwindSafe(|| { self.call_convert_value(rb_self, #(arg~N,)*) })) { Ok(v) => v, Err(e) => Err(Error::from_panic(e)), }; match res { Ok(v) => v, Err(e) => raise(e), } } } impl $name for Func where Func: Fn(RbSelf, #(T~N,)*) -> Res, RbSelf: TryConvert, #(T~N: TryConvert,)* Res: ReturnValue, {} /// Helper trait for wrapping a function as a Ruby method taking /// [`&Ruby`](Ruby), self, and N arguments, with type conversions /// and error handling. /// /// See the [`method`](crate::method!) macro. #[doc(hidden)] pub trait $ruby_name where Self: Sized + Fn(&Ruby, RbSelf, #(T~N,)*) -> Res, RbSelf: TryConvert, #(T~N: TryConvert,)* Res: ReturnValue, { #[inline] fn call_convert_value(self, rb_self: Value, #(arg~N: Value,)*) -> Result { (self)( &Ruby::get_with(rb_self), TryConvert::try_convert(rb_self)?, #(TryConvert::try_convert(arg~N)?,)* ).into_return_value() } #[inline] unsafe fn call_handle_error(self, rb_self: Value, #(arg~N: Value,)*) -> Value { let res = match std::panic::catch_unwind(AssertUnwindSafe(|| { self.call_convert_value(rb_self, #(arg~N,)*) })) { Ok(v) => v, Err(e) => Err(Error::from_panic(e)), }; match res { Ok(v) => v, Err(e) => raise(e), } } } impl $ruby_name for Func where Func: Fn(&Ruby, RbSelf, #(T~N,)*) -> Res, RbSelf: TryConvert, #(T~N: TryConvert,)* Res: ReturnValue, {} }); } } seq!(N in 0..=16 { method_n!(Method~N, RubyMethod~N, N); }); /// Wrap a Rust function item with Ruby type conversion and error handling. /// /// This macro wraps the given function and returns a function pointer /// implementing the [`Method`] trait, suitable for passing to functions that /// define Ruby methods such as /// [`define_method`](crate::module::Module::define_method). /// /// Ruby code implicitly always has a `self` parameter available. In the /// extention API this is passed explicitly. As a result there is always an /// extra `self` argument before the arguments explitly passed in Ruby, and the /// number of Rust argument will be one more than the Ruby arity. /// /// The values `-2` and `-1` for `arity` have special meaning. Both indicate /// functions with any number of arguments, with `-2` the arguments are passed /// as a [`RArray`], with `-1` they are passed as a slice of [`Value`]s. /// Arity of `-1` can be used with [`scan_args`](crate::scan_args::scan_args) /// and [`get_kwargs`](crate::scan_args::get_kwargs) for more complex method /// signatures. /// /// | Arity | Signature | /// |-------|-----------------------------------------------------------| /// | -2 | `fn(rb_self: T, arguments: RArray) -> Result` | /// | -1 | `fn(rb_self: T, arguments: &[Value]) -> Result` | /// | 0 | `fn(rb_self: T) -> Result` | /// | 1 | `fn(rb_self: T, arg1: U) -> Result` | /// | 2 | `fn(rb_self: T, arg1: U, arg2: V) -> Result` | /// | ... | ... | /// | 16 | ... | /// /// Where `T`, `U`, `V` and so on are any types that implement `TryConvert`, /// and `R` implements [`IntoValue`]. It is also possible to return just `R` /// rather than a `Result` for functions that will never error, and omit the /// return value (i.e. return `()`) for a function that returns `nil` to Ruby. /// See [`ReturnValue`] for more details on what can be returned. /// /// See the [`function`](crate::function!) macro for cases where there is no /// need to handle the `self` argument. /// /// # Examples /// /// ``` /// use magnus::{method, prelude::*, Error, Ruby}; /// /// fn rb_is_blank(rb_self: String) -> bool { /// rb_self.contains(|c: char| !c.is_whitespace()) /// } /// /// #[magnus::init] /// fn init(ruby: &Ruby) -> Result<(), Error> { /// let class = ruby.define_class("String", ruby.class_object())?; /// class.define_method("blank?", method!(rb_is_blank, 0))?; /// Ok(()) /// } /// # let cleanup = unsafe { magnus::embed::init() }; /// # init(&cleanup).unwrap(); /// ``` #[macro_export] macro_rules! method { ($name:expr, -2) => {{ unsafe extern "C" fn anon(rb_self: $crate::Value, args: $crate::RArray) -> $crate::Value { use $crate::method::{MethodRbAry, RubyMethodRbAry}; $name.call_handle_error(rb_self, args) } anon as unsafe extern "C" fn($crate::Value, $crate::RArray) -> $crate::Value }}; ($name:expr, -1) => {{ unsafe extern "C" fn anon( argc: std::os::raw::c_int, argv: *const $crate::Value, rb_self: $crate::Value, ) -> $crate::Value { use $crate::method::{MethodCAry, RubyMethodCAry}; $name.call_handle_error(argc, argv, rb_self) } anon as unsafe extern "C" fn( std::os::raw::c_int, *const $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 0) => {{ unsafe extern "C" fn anon(rb_self: $crate::Value) -> $crate::Value { use $crate::method::{Method0, RubyMethod0}; $name.call_handle_error(rb_self) } anon as unsafe extern "C" fn($crate::Value) -> $crate::Value }}; ($name:expr, 1) => {{ unsafe extern "C" fn anon(rb_self: $crate::Value, a: $crate::Value) -> $crate::Value { use $crate::method::{Method1, RubyMethod1}; $name.call_handle_error(rb_self, a) } anon as unsafe extern "C" fn($crate::Value, $crate::Value) -> $crate::Value }}; ($name:expr, 2) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, ) -> $crate::Value { use $crate::method::{Method2, RubyMethod2}; $name.call_handle_error(rb_self, a, b) } anon as unsafe extern "C" fn($crate::Value, $crate::Value, $crate::Value) -> $crate::Value }}; ($name:expr, 3) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, ) -> $crate::Value { use $crate::method::{Method3, RubyMethod3}; $name.call_handle_error(rb_self, a, b, c) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 4) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, ) -> $crate::Value { use $crate::method::{Method4, RubyMethod4}; $name.call_handle_error(rb_self, a, b, c, d) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 5) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, ) -> $crate::Value { use $crate::method::{Method5, RubyMethod5}; $name.call_handle_error(rb_self, a, b, c, d, e) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 6) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, ) -> $crate::Value { use $crate::method::{Method6, RubyMethod6}; $name.call_handle_error(rb_self, a, b, c, d, e, f) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 7) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, g: $crate::Value, ) -> $crate::Value { use $crate::method::{Method7, RubyMethod7}; $name.call_handle_error(rb_self, a, b, c, d, e, f, g) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 8) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, g: $crate::Value, h: $crate::Value, ) -> $crate::Value { use $crate::method::{Method8, RubyMethod8}; $name.call_handle_error(rb_self, a, b, c, d, e, f, g, h) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 9) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, g: $crate::Value, h: $crate::Value, i: $crate::Value, ) -> $crate::Value { use $crate::method::{Method9, RubyMethod9}; $name.call_handle_error(rb_self, a, b, c, d, e, f, g, h, i) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 10) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, g: $crate::Value, h: $crate::Value, i: $crate::Value, j: $crate::Value, ) -> $crate::Value { use $crate::method::{Method10, RubyMethod10}; $name.call_handle_error(rb_self, a, b, c, d, e, f, g, h, i, j) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 11) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, g: $crate::Value, h: $crate::Value, i: $crate::Value, j: $crate::Value, k: $crate::Value, ) -> $crate::Value { use $crate::method::{Method11, RubyMethod11}; $name.call_handle_error(rb_self, a, b, c, d, e, f, g, h, i, j, k) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 12) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, g: $crate::Value, h: $crate::Value, i: $crate::Value, j: $crate::Value, k: $crate::Value, l: $crate::Value, ) -> $crate::Value { use $crate::method::{Method12, RubyMethod12}; $name.call_handle_error(rb_self, a, b, c, d, e, f, g, h, i, j, k, l) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 13) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, g: $crate::Value, h: $crate::Value, i: $crate::Value, j: $crate::Value, k: $crate::Value, l: $crate::Value, m: $crate::Value, ) -> $crate::Value { use $crate::method::{Method13, RubyMethod13}; $name.call_handle_error(rb_self, a, b, c, d, e, f, g, h, i, j, k, l, m) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 14) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, g: $crate::Value, h: $crate::Value, i: $crate::Value, j: $crate::Value, k: $crate::Value, l: $crate::Value, m: $crate::Value, n: $crate::Value, ) -> $crate::Value { use $crate::method::{Method14, RubyMethod14}; $name.call_handle_error(rb_self, a, b, c, d, e, f, g, h, i, j, k, l, m, n) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 15) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, g: $crate::Value, h: $crate::Value, i: $crate::Value, j: $crate::Value, k: $crate::Value, l: $crate::Value, m: $crate::Value, n: $crate::Value, o: $crate::Value, ) -> $crate::Value { use $crate::method::{Method15, RubyMethod15}; $name.call_handle_error(rb_self, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 16) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, g: $crate::Value, h: $crate::Value, i: $crate::Value, j: $crate::Value, k: $crate::Value, l: $crate::Value, m: $crate::Value, n: $crate::Value, o: $crate::Value, p: $crate::Value, ) -> $crate::Value { use $crate::method::{Method16, RubyMethod16}; $name.call_handle_error(rb_self, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, $arity:expr) => { compile_error!("arity must be an integer literal between -2..=16") }; } /// Helper trait for wrapping a function as a Ruby method ignoring self and /// taking a Ruby array of arguments, with type conversions and error handling. /// /// See the [`function`](crate::function!) macro. #[doc(hidden)] pub trait FunctionRbAry where Self: Sized + Fn(Args) -> Res, Args: TryConvert, Res: ReturnValue, { #[inline] fn call_convert_value(self, args: RArray) -> Result { (self)(TryConvert::try_convert(args.as_value())?).into_return_value() } #[inline] unsafe fn call_handle_error(self, args: RArray) -> Value { let res = match std::panic::catch_unwind(AssertUnwindSafe(|| self.call_convert_value(args))) { Ok(v) => v, Err(e) => Err(Error::from_panic(e)), }; match res { Ok(v) => v, Err(e) => raise(e), } } } impl FunctionRbAry for Func where Func: Fn(Args) -> Res, Args: TryConvert, Res: ReturnValue, { } /// Helper trait for wrapping a function as a Ruby method taking /// [`&Ruby`](Ruby), ignoring self, and taking a Ruby array of arguments, with /// type conversions and error handling. /// /// See the [`function`](crate::function!) macro. #[doc(hidden)] pub trait RubyFunctionRbAry where Self: Sized + Fn(&Ruby, Args) -> Res, Args: TryConvert, Res: ReturnValue, { #[inline] fn call_convert_value(self, args: RArray) -> Result { (self)( &Ruby::get_with(args), TryConvert::try_convert(args.as_value())?, ) .into_return_value() } #[inline] unsafe fn call_handle_error(self, args: RArray) -> Value { let res = match std::panic::catch_unwind(AssertUnwindSafe(|| self.call_convert_value(args))) { Ok(v) => v, Err(e) => Err(Error::from_panic(e)), }; match res { Ok(v) => v, Err(e) => raise(e), } } } impl RubyFunctionRbAry for Func where Func: Fn(&Ruby, Args) -> Res, Args: TryConvert, Res: ReturnValue, { } /// Helper trait for wrapping a function as a Ruby method ignoring self and /// taking a slice of arguments, with type conversions and error handling. /// /// See the [`function`](crate::function!) macro. #[doc(hidden)] pub trait FunctionCAry where Self: Sized + Fn(&[Value]) -> Res, Res: ReturnValue, { #[inline] unsafe fn call_convert_value(self, argc: c_int, argv: *const Value) -> Result { let args = slice::from_raw_parts(argv, argc as usize); (self)(args).into_return_value() } #[inline] unsafe fn call_handle_error(self, argc: c_int, argv: *const Value) -> Value { let res = match std::panic::catch_unwind(AssertUnwindSafe(|| { self.call_convert_value(argc, argv) })) { Ok(v) => v, Err(e) => Err(Error::from_panic(e)), }; match res { Ok(v) => v, Err(e) => raise(e), } } } impl FunctionCAry for Func where Func: Fn(&[Value]) -> Res, Res: ReturnValue, { } /// Helper trait for wrapping a function as a Ruby method taking /// [`&Ruby`](Ruby), ignoring self, and taking a slice of arguments, with type /// conversions and error handling. /// /// See the [`function`](crate::function!) macro. #[doc(hidden)] pub trait RubyFunctionCAry where Self: Sized + Fn(&Ruby, &[Value]) -> Res, Res: ReturnValue, { #[inline] unsafe fn call_convert_value(self, argc: c_int, argv: *const Value) -> Result { let args = slice::from_raw_parts(argv, argc as usize); (self)(&Ruby::get_unchecked(), args).into_return_value() } #[inline] unsafe fn call_handle_error(self, argc: c_int, argv: *const Value) -> Value { let res = match std::panic::catch_unwind(AssertUnwindSafe(|| { self.call_convert_value(argc, argv) })) { Ok(v) => v, Err(e) => Err(Error::from_panic(e)), }; match res { Ok(v) => v, Err(e) => raise(e), } } } impl RubyFunctionCAry for Func where Func: Fn(&Ruby, &[Value]) -> Res, Res: ReturnValue, { } macro_rules! function_n { ($name:ident, $ruby_name:ident, $n:literal) => { seq!(N in 0..$n { /// Helper trait for wrapping a function as a Ruby method ignoring /// self and taking N arguments, with type conversions and error /// handling. /// /// See the [`function`](crate::function!) macro. #[doc(hidden)] pub trait $name<#(T~N,)* Res> where Self: Sized + Fn(#(T~N,)*) -> Res, #(T~N: TryConvert,)* Res: ReturnValue, { #[inline] fn call_convert_value(self, #(arg~N: Value,)*) -> Result { (self)( #(TryConvert::try_convert(arg~N)?,)* ).into_return_value() } #[inline] unsafe fn call_handle_error(self, #(arg~N: Value,)*) -> Value { let res = match std::panic::catch_unwind(AssertUnwindSafe(|| { self.call_convert_value(#(arg~N,)*) })) { Ok(v) => v, Err(e) => Err(Error::from_panic(e)), }; match res { Ok(v) => v, Err(e) => raise(e), } } } impl $name<#(T~N,)* Res> for Func where Func: Fn(#(T~N,)*) -> Res, #(T~N: TryConvert,)* Res: ReturnValue, {} /// Helper trait for wrapping a function as a Ruby method taking /// [`&Ruby`](Ruby), ignoring self, and taking N arguments, with /// type conversions and error handling. /// /// See the [`function`](crate::function!) macro. #[doc(hidden)] pub trait $ruby_name<#(T~N,)* Res> where Self: Sized + Fn(&Ruby, #(T~N,)*) -> Res, #(T~N: TryConvert,)* Res: ReturnValue, { #[inline] unsafe fn call_convert_value(self, #(arg~N: Value,)*) -> Result { (self)( &Ruby::get_unchecked(), #(TryConvert::try_convert(arg~N)?,)* ).into_return_value() } #[inline] unsafe fn call_handle_error(self, #(arg~N: Value,)*) -> Value { let res = match std::panic::catch_unwind(AssertUnwindSafe(|| { self.call_convert_value(#(arg~N,)*) })) { Ok(v) => v, Err(e) => Err(Error::from_panic(e)), }; match res { Ok(v) => v, Err(e) => raise(e), } } } impl $ruby_name<#(T~N,)* Res> for Func where Func: Fn(&Ruby, #(T~N,)*) -> Res, #(T~N: TryConvert,)* Res: ReturnValue, {} }); } } seq!(N in 0..=16 { function_n!(Function~N, RubyFunction~N, N); }); /// Wrap a Rust function item with Ruby type conversion and error handling, /// ignoring Ruby's `self` argument. /// /// This macro wraps the given function and returns a function pointer /// implementing the [`Method`] trait, suitable for passing to functions that /// define Ruby methods such as /// [`define_method`](crate::module::Module::define_method). /// /// Ruby code implicitly always has a `self` parameter available. In the /// extention API this is passed explicitly. The wrapper this macro generates /// ignores that argument, and does not pass it to the wrapped function. /// /// The values `-2` and `-1` for `arity` have special meaning. Both indicate /// functions with any number of arguments, with `-2` the arguments are passed /// as a [`RArray`], with `-1` they are passed as a slice of [`Value`]s. /// Arity of `-1` can be used with [`scan_args`](crate::scan_args::scan_args) /// and [`get_kwargs`](crate::scan_args::get_kwargs) for more complex method /// signatures. /// /// | Arity | Signature | /// |-------|-----------------------------------------------| /// | -2 | `fn(arguments: RArray) -> Result` | /// | -1 | `fn(arguments: &[Value]) -> Result` | /// | 0 | `fn()-> Result` | /// | 1 | `fn(arg1: T) -> Result` | /// | 2 | `fn(arg1: T, arg2: U) -> Result` | /// | ... | ... | /// | 16 | ... | /// /// Where `T`, `U`, and so on are any types that implement `TryConvert`, /// and `R` implements [`IntoValue`]. It is also possible to return just `R` /// rather than a `Result` for functions that will never error, and omit the /// return value (i.e. return `()`) for a function that returns `nil` to Ruby. /// See [`ReturnValue`] for more details on what can be returned. /// /// See the [`method`](crate::method!) macro for cases where the `self` /// argument is required. /// /// # Examples /// /// ``` /// fn distance(a: (f64, f64), b: (f64, f64)) -> f64 { /// ((b.0 - a.0).powi(2) + (b.0 - a.0).powi(2)).sqrt() /// } /// /// #[magnus::init] /// fn init(ruby: &magnus::Ruby) { /// ruby.define_global_function("distance", magnus::function!(distance, 2)); /// } /// # let cleanup = unsafe { magnus::embed::init() }; /// # init(&cleanup); /// ``` #[macro_export] macro_rules! function { ($name:expr, -2) => {{ unsafe extern "C" fn anon(rb_self: $crate::Value, args: $crate::RArray) -> $crate::Value { use $crate::method::{FunctionRbAry, RubyFunctionRbAry}; $name.call_handle_error(args) } anon as unsafe extern "C" fn($crate::Value, $crate::RArray) -> $crate::Value }}; ($name:expr, -1) => {{ unsafe extern "C" fn anon( argc: std::os::raw::c_int, argv: *const $crate::Value, rb_self: $crate::Value, ) -> $crate::Value { use $crate::method::{FunctionCAry, RubyFunctionCAry}; $name.call_handle_error(argc, argv) } anon as unsafe extern "C" fn( std::os::raw::c_int, *const $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 0) => {{ unsafe extern "C" fn anon(rb_self: $crate::Value) -> $crate::Value { use $crate::method::{Function0, RubyFunction0}; $name.call_handle_error() } anon as unsafe extern "C" fn($crate::Value) -> $crate::Value }}; ($name:expr, 1) => {{ unsafe extern "C" fn anon(rb_self: $crate::Value, a: $crate::Value) -> $crate::Value { use $crate::method::{Function1, RubyFunction1}; $name.call_handle_error(a) } anon as unsafe extern "C" fn($crate::Value, $crate::Value) -> $crate::Value }}; ($name:expr, 2) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, ) -> $crate::Value { use $crate::method::{Function2, RubyFunction2}; $name.call_handle_error(a, b) } anon as unsafe extern "C" fn($crate::Value, $crate::Value, $crate::Value) -> $crate::Value }}; ($name:expr, 3) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, ) -> $crate::Value { use $crate::method::{Function3, RubyFunction3}; $name.call_handle_error(a, b, c) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 4) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, ) -> $crate::Value { use $crate::method::{Function4, RubyFunction4}; $name.call_handle_error(a, b, c, d) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 5) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, ) -> $crate::Value { use $crate::method::{Function5, RubyFunction5}; $name.call_handle_error(a, b, c, d, e) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 6) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, ) -> $crate::Value { use $crate::method::{Function6, RubyFunction6}; $name.call_handle_error(a, b, c, d, e, f) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 7) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, g: $crate::Value, ) -> $crate::Value { use $crate::method::{Function7, RubyFunction7}; $name.call_handle_error(a, b, c, d, e, f, g) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 8) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, g: $crate::Value, h: $crate::Value, ) -> $crate::Value { use $crate::method::{Function8, RubyFunction8}; $name.call_handle_error(a, b, c, d, e, f, g, h) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 9) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, g: $crate::Value, h: $crate::Value, i: $crate::Value, ) -> $crate::Value { use $crate::method::{Function9, RubyFunction9}; $name.call_handle_error(a, b, c, d, e, f, g, h, i) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 10) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, g: $crate::Value, h: $crate::Value, i: $crate::Value, j: $crate::Value, ) -> $crate::Value { use $crate::method::{Function10, RubyFunction10}; $name.call_handle_error(a, b, c, d, e, f, g, h, i, j) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 11) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, g: $crate::Value, h: $crate::Value, i: $crate::Value, j: $crate::Value, k: $crate::Value, ) -> $crate::Value { use $crate::method::{Function11, RubyFunction11}; $name.call_handle_error(a, b, c, d, e, f, g, h, i, j, k) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 12) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, g: $crate::Value, h: $crate::Value, i: $crate::Value, j: $crate::Value, k: $crate::Value, l: $crate::Value, ) -> $crate::Value { use $crate::method::{Function12, RubyFunction12}; $name.call_handle_error(a, b, c, d, e, f, g, h, i, j, k, l) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 13) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, g: $crate::Value, h: $crate::Value, i: $crate::Value, j: $crate::Value, k: $crate::Value, l: $crate::Value, m: $crate::Value, ) -> $crate::Value { use $crate::method::{Function13, RubyFunction13}; $name.call_handle_error(a, b, c, d, e, f, g, h, i, j, k, l, m) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 14) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, g: $crate::Value, h: $crate::Value, i: $crate::Value, j: $crate::Value, k: $crate::Value, l: $crate::Value, m: $crate::Value, n: $crate::Value, ) -> $crate::Value { use $crate::method::{Function14, RubyFunction14}; $name.call_handle_error(a, b, c, d, e, f, g, h, i, j, k, l, m, n) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 15) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, g: $crate::Value, h: $crate::Value, i: $crate::Value, j: $crate::Value, k: $crate::Value, l: $crate::Value, m: $crate::Value, n: $crate::Value, o: $crate::Value, ) -> $crate::Value { use $crate::method::{Function15, RubyFunction15}; $name.call_handle_error(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, 16) => {{ unsafe extern "C" fn anon( rb_self: $crate::Value, a: $crate::Value, b: $crate::Value, c: $crate::Value, d: $crate::Value, e: $crate::Value, f: $crate::Value, g: $crate::Value, h: $crate::Value, i: $crate::Value, j: $crate::Value, k: $crate::Value, l: $crate::Value, m: $crate::Value, n: $crate::Value, o: $crate::Value, p: $crate::Value, ) -> $crate::Value { use $crate::method::{Function16, RubyFunction16}; $name.call_handle_error(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) } anon as unsafe extern "C" fn( $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, $crate::Value, ) -> $crate::Value }}; ($name:expr, $arity:expr) => { compile_error!("arity must be an integer literal between -2..=16") }; } magnus-0.7.1/src/module.rs000064400000000000000000000771471046102023000135650ustar 00000000000000//! Types and functions for working with Ruby modules. //! //! See also [`Ruby`](Ruby#core-modules) for more module related methods. use std::{ffi::CString, fmt, mem::transmute, os::raw::c_int}; use rb_sys::{ rb_alias, rb_attr, rb_class_inherited_p, rb_const_get, rb_const_set, rb_define_class_id_under, rb_define_method_id, rb_define_module_function, rb_define_module_id_under, rb_define_private_method, rb_define_protected_method, rb_include_module, rb_mComparable, rb_mEnumerable, rb_mErrno, rb_mFileTest, rb_mGC, rb_mKernel, rb_mMath, rb_mProcess, rb_mWaitReadable, rb_mWaitWritable, rb_mod_ancestors, rb_module_new, rb_prepend_module, ruby_value_type, VALUE, }; use crate::{ class::{Class, RClass}, error::{protect, Error}, exception::ExceptionClass, into_value::IntoValue, method::Method, object::Object, r_array::RArray, try_convert::TryConvert, value::{ private::{self, ReprValue as _}, IntoId, NonZeroValue, ReprValue, Value, }, Ruby, }; /// # `RModule` /// /// Functions that can be used to create Ruby modules. /// /// See also the [`RModule`] type. impl Ruby { /// Create a new anonymous module. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let module = ruby.module_new(); /// assert!(module.is_kind_of(ruby.class_module())); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn module_new(&self) -> RModule { unsafe { RModule::from_rb_value_unchecked(rb_module_new()) } } } /// A Value pointer to a RModule struct, Ruby's internal representation of /// modules. /// /// See the [`Module`] trait for defining instance methods and nested /// classes/modules. /// See the [`Object`] trait for defining singlton methods (aka class methods). /// /// See the [`ReprValue`] trait for additional methods available on this type. /// See [`Ruby`](Ruby#rmodule) for methods to create an `RModule`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct RModule(NonZeroValue); impl RModule { /// Return `Some(RModule)` if `val` is a `RModule`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, RModule}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(RModule::from_value(eval("Enumerable").unwrap()).is_some()); /// assert!(RModule::from_value(eval("String").unwrap()).is_none()); /// assert!(RModule::from_value(eval("nil").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { (val.rb_type() == ruby_value_type::RUBY_T_MODULE) .then(|| Self(NonZeroValue::new_unchecked(val))) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } /// Create a new anonymous module. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::module_new`] for /// the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{class, prelude::*, RModule}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let module = RModule::new(); /// assert!(module.is_kind_of(class::module())); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::module_new` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn new() -> Self { get_ruby!().module_new() } /// Define a method in `self`'s scope as a 'module function'. This method /// will be visible as a public 'class' method on the module and a private /// instance method on any object including the module. /// /// # Examples /// /// ``` /// use magnus::{function, r_string, rb_assert, Error, RString, Ruby}; /// /// fn greet(ruby: &Ruby) -> RString { /// r_string!(ruby, "Hello, world!") /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let module = ruby.define_module("Greeting")?; /// module.define_module_function("greet", function!(greet, 0))?; /// /// rb_assert!(ruby, r#"Greeting.greet == "Hello, world!""#); /// /// rb_assert!( /// ruby, /// r#" /// include Greeting /// greet == "Hello, world!" /// "#, /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn define_module_function(self, name: &str, func: M) -> Result<(), Error> where M: Method, { debug_assert_value!(self); let name = CString::new(name).unwrap(); protect(|| { unsafe { rb_define_module_function( self.as_rb_value(), name.as_ptr(), transmute(func.as_ptr()), M::arity().into(), ) }; Ruby::get_with(self).qnil() })?; Ok(()) } } impl fmt::Display for RModule { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for RModule { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for RModule { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl Object for RModule {} impl Module for RModule {} unsafe impl private::ReprValue for RModule {} impl ReprValue for RModule {} impl TryConvert for RModule { fn try_convert(val: Value) -> Result { Self::from_value(val).ok_or_else(|| { Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into Module", unsafe { val.classname() },), ) }) } } /// Functions available on both classes and modules. pub trait Module: Object + ReprValue + Copy { /// Define a class in `self`'s scope. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let outer = ruby.define_module("Outer")?; /// outer.define_class("Inner", ruby.class_object())?; /// rb_assert!(ruby, "Outer::Inner.is_a?(Class)"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn define_class(self, name: T, superclass: RClass) -> Result where T: IntoId, { debug_assert_value!(self); debug_assert_value!(superclass); let id = name.into_id_with(&Ruby::get_with(self)); let superclass = superclass.as_rb_value(); protect(|| unsafe { RClass::from_rb_value_unchecked(rb_define_class_id_under( self.as_rb_value(), id.as_rb_id(), superclass, )) }) } /// Define a module in `self`'s scope. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let outer = ruby.define_module("Outer")?; /// outer.define_module("Inner")?; /// rb_assert!(ruby, "Outer::Inner.is_a?(Module)"); /// rb_assert!(ruby, "!Outer::Inner.is_a?(Class)"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn define_module(self, name: T) -> Result where T: IntoId, { let id = name.into_id_with(&Ruby::get_with(self)); protect(|| unsafe { RModule::from_rb_value_unchecked(rb_define_module_id_under( self.as_rb_value(), id.as_rb_id(), )) }) } /// Define an exception class in `self`'s scope. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let outer = ruby.define_module("Outer")?; /// outer.define_error("InnerError", ruby.exception_standard_error())?; /// rb_assert!(ruby, "Outer::InnerError.is_a?(Class)"); /// rb_assert!(ruby, "Outer::InnerError < Exception"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn define_error(self, name: T, superclass: ExceptionClass) -> Result where T: IntoId, { self.define_class(name, superclass.as_r_class()) .map(|c| unsafe { ExceptionClass::from_value_unchecked(c.as_value()) }) } /// Include `module` into `self`. /// /// Effectively makes `module` the superclass of `self`. See also /// [`prepend_module`](Module::prepend_module). /// /// # Examples /// /// ``` /// use magnus::{function, prelude::*, rb_assert, Error, RClass, Ruby}; /// /// fn test() -> i64 { /// 42 /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let module = ruby.module_new(); /// module.define_method("test", function!(test, 0))?; /// /// let class = RClass::new(ruby.class_object())?; /// class.include_module(module)?; /// /// let obj = class.new_instance(())?; /// rb_assert!(ruby, "obj.test == 42", obj); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn include_module(self, module: RModule) -> Result<(), Error> { protect(|| { unsafe { rb_include_module(self.as_rb_value(), module.as_rb_value()) }; Ruby::get_with(self).qnil() })?; Ok(()) } /// Prepend `self` with `module`. /// /// Similar to [`include_module`](Module::include_module), but inserts /// `module` as if it were a subclass in the inheritance chain. /// /// # Examples /// /// ``` /// use magnus::{function, prelude::*, rb_assert, Error, RClass, Ruby}; /// /// fn test(ruby: &Ruby) -> Result { /// Ok(ruby.call_super::<_, i64>(())? + 2) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let module = ruby.module_new(); /// module.define_method("test", function!(test, 0))?; /// /// let class: RClass = ruby.eval( /// r#" /// class Example /// def test /// 40 /// end /// end /// Example /// "#, /// )?; /// class.prepend_module(module)?; /// /// let obj = class.new_instance(())?; /// rb_assert!("obj.test == 42", obj); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn prepend_module(self, module: RModule) -> Result<(), Error> { protect(|| { unsafe { rb_prepend_module(self.as_rb_value(), module.as_rb_value()) }; Ruby::get_with(self).qnil() })?; Ok(()) } /// Set the value for the constant `name` within `self`'s scope. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Module, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.class_array().const_set("EXAMPLE", 42)?; /// /// rb_assert!(ruby, "Array::EXAMPLE == 42"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn const_set(self, name: T, value: U) -> Result<(), Error> where T: IntoId, U: IntoValue, { let handle = Ruby::get_with(self); let id = name.into_id_with(&handle); let val = value.into_value_with(&handle); protect(|| { unsafe { rb_const_set(self.as_rb_value(), id.as_rb_id(), val.as_rb_value()) }; handle.qnil() })?; Ok(()) } /// Get the value for the constant `name` within `self`'s scope. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Module, RClass, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.eval::( /// " /// class Example /// VALUE = 42 /// end /// ", /// )?; /// /// let class = ruby.class_object().const_get::<_, RClass>("Example")?; /// rb_assert!(ruby, "klass::VALUE == 42", klass = class); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn const_get(self, name: T) -> Result where T: IntoId, U: TryConvert, { debug_assert_value!(self); let id = name.into_id_with(&Ruby::get_with(self)); let res = unsafe { protect(|| Value::new(rb_const_get(self.as_rb_value(), id.as_rb_id()))) }; res.and_then(TryConvert::try_convert) } /// Returns whether or not `self` inherits from `other`. /// /// Classes including a module are considered to inherit from that module. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Module, RClass, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = RClass::new(ruby.class_object())?; /// let b = RClass::new(a)?; /// assert!(b.is_inherited(a)); /// assert!(!a.is_inherited(b)); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn is_inherited(self, other: T) -> bool where T: ReprValue + Module, { unsafe { Value::new(rb_class_inherited_p( self.as_rb_value(), other.as_rb_value(), )) .to_bool() } } /// Return the classes and modules `self` inherits, includes, or prepends. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Module, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.class_string().ancestors(); /// /// rb_assert!( /// ruby, /// "ary == [String, Comparable, Object, Kernel, BasicObject]", /// ary, /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn ancestors(self) -> RArray { unsafe { RArray::from_rb_value_unchecked(rb_mod_ancestors(self.as_rb_value())) } } /// Define a method in `self`'s scope. /// /// # Examples /// /// ``` /// use magnus::{method, rb_assert, Error, Module, Ruby}; /// /// fn escape_unicode(s: String) -> String { /// s.escape_unicode().to_string() /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.class_string() /// .define_method("escape_unicode", method!(escape_unicode, 0))?; /// /// rb_assert!( /// ruby, /// r#""🤖\etest".escape_unicode == "\\u{1f916}\\u{1b}\\u{74}\\u{65}\\u{73}\\u{74}""#, /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn define_method(self, name: T, func: M) -> Result<(), Error> where T: IntoId, M: Method, { debug_assert_value!(self); let handle = Ruby::get_with(self); let id = name.into_id_with(&handle); protect(|| { unsafe { rb_define_method_id( self.as_rb_value(), id.as_rb_id(), transmute(func.as_ptr()), M::arity().into(), ) }; handle.qnil() })?; Ok(()) } /// Define a private method in `self`'s scope. /// /// # Examples /// /// ``` /// use magnus::{eval, function, rb_assert, Error, Module, Ruby, Value}; /// /// fn percent_encode(c: char) -> String { /// if c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.' || c == '~' { /// String::from(c) /// } else { /// format!("%{:X}", c as u32) /// } /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.class_string() /// .define_private_method("percent_encode_char", function!(percent_encode, 1))?; /// /// ruby.eval::( /// r#" /// class String /// def percent_encode /// chars.map {|c| percent_encode_char(c)}.join("") /// end /// end /// "#, /// )?; /// /// rb_assert!(ruby, r#""foo bar".percent_encode == "foo%20bar""#); /// /// assert!(eval::(r#"" ".percent_encode_char(" ")"#) /// .unwrap_err() /// .is_kind_of(ruby.exception_no_method_error())); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn define_private_method(self, name: &str, func: M) -> Result<(), Error> where M: Method, { debug_assert_value!(self); let name = CString::new(name).unwrap(); protect(|| { unsafe { rb_define_private_method( self.as_rb_value(), name.as_ptr(), transmute(func.as_ptr()), M::arity().into(), ) }; Ruby::get_with(self).qnil() })?; Ok(()) } /// Define a protected method in `self`'s scope. /// /// # Examples /// /// ``` /// use magnus::{method, rb_assert, Error, Module, Ruby, Value}; /// /// fn escape_unicode(s: String) -> String { /// s.escape_unicode().to_string() /// } /// /// fn is_invisible(c: char) -> bool { /// c.is_control() || c.is_whitespace() /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.class_string() /// .define_method("escape_unicode", method!(escape_unicode, 0))?; /// ruby.class_string() /// .define_protected_method("invisible?", method!(is_invisible, 0))?; /// /// ruby.eval::( /// r#" /// class String /// def escape_invisible /// chars.map {|c| c.invisible? ? c.escape_unicode : c}.join("") /// end /// end /// "#, /// )?; /// /// rb_assert!( /// ruby, /// r#""🤖\tfoo bar".escape_invisible == "🤖\\u{9}foo\\u{20}bar""#, /// ); /// /// assert!(ruby /// .eval::(r#"" ".invisible?"#) /// .unwrap_err() /// .is_kind_of(ruby.exception_no_method_error())); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn define_protected_method(self, name: &str, func: M) -> Result<(), Error> where M: Method, { debug_assert_value!(self); let name = CString::new(name).unwrap(); protect(|| { unsafe { rb_define_protected_method( self.as_rb_value(), name.as_ptr(), transmute(func.as_ptr()), M::arity().into(), ) }; Ruby::get_with(self).qnil() })?; Ok(()) } /// Define public accessor methods for the attribute `name`. /// /// `name` should be **without** the preceding `@`. /// /// # Examples /// /// ``` /// use magnus::{eval, prelude::*, rb_assert, Attr, Error, Module, RClass, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let class = RClass::new(ruby.class_object())?; /// class.define_attr("example", Attr::ReadWrite)?; /// /// let obj = class.new_instance(())?; /// let _: Value = eval!(ruby, "obj.example = 42", obj)?; /// rb_assert!(ruby, "obj.example == 42", obj); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn define_attr(self, name: T, rw: Attr) -> Result<(), Error> where T: IntoId, { let handle = Ruby::get_with(self); let id = name.into_id_with(&handle); protect(|| { unsafe { rb_attr( self.as_rb_value(), id.as_rb_id(), rw.is_read() as c_int, rw.is_write() as c_int, 0, ) }; handle.qnil() })?; Ok(()) } /// Alias the method `src` of `self` as `dst`. /// /// # Examples /// /// ``` /// use magnus::{function, prelude::*, rb_assert, Error, Module, RClass, Ruby}; /// /// fn test() -> i64 { /// 42 /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let class = RClass::new(ruby.class_object())?; /// class.define_method("test", function!(test, 0))?; /// class.define_alias("example", "test")?; /// /// let obj = class.new_instance(())?; /// rb_assert!(ruby, "obj.example == 42", obj); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn define_alias(self, dst: T, src: U) -> Result<(), Error> where T: IntoId, U: IntoId, { let handle = Ruby::get_with(self); let d_id = dst.into_id_with(&handle); let s_id = src.into_id_with(&handle); protect(|| { unsafe { rb_alias(self.as_rb_value(), d_id.as_rb_id(), s_id.as_rb_id()) }; handle.qnil() })?; Ok(()) } } /// Argument for [`define_attr`](Module::define_attr). #[derive(Clone, Copy, Debug)] pub enum Attr { /// Define a reader method like `name`. Read, /// Define a writer method like `name=`. Write, /// Define both reader and writer methods like `name` and `name=`. ReadWrite, } impl Attr { fn is_read(self) -> bool { match self { Attr::Read | Attr::ReadWrite => true, Attr::Write => false, } } fn is_write(self) -> bool { match self { Attr::Write | Attr::ReadWrite => true, Attr::Read => false, } } } /// # Core Modules /// /// Functions to access Ruby's built-in modules. /// /// See also [`Ruby::define_module`] and the [`module`](self) module. impl Ruby { /// Return Ruby's `Comparable` module. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "md == Comparable", md = ruby.module_comparable()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn module_comparable(&self) -> RModule { unsafe { RModule::from_rb_value_unchecked(rb_mComparable) } } /// Return Ruby's `Enumerable` module. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "md == Enumerable", md = ruby.module_enumerable()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn module_enumerable(&self) -> RModule { unsafe { RModule::from_rb_value_unchecked(rb_mEnumerable) } } /// Return Ruby's `Errno` module. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "md == Errno", md = ruby.module_errno()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn module_errno(&self) -> RModule { unsafe { RModule::from_rb_value_unchecked(rb_mErrno) } } /// Return Ruby's `FileTest` module. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "md == FileTest", md = ruby.module_file_test()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn module_file_test(&self) -> RModule { unsafe { RModule::from_rb_value_unchecked(rb_mFileTest) } } /// Return Ruby's `GC` module. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "md == GC", md = ruby.module_gc()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn module_gc(&self) -> RModule { unsafe { RModule::from_rb_value_unchecked(rb_mGC) } } /// Return Ruby's `Kernel` module. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "md == Kernel", md = ruby.module_kernel()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn module_kernel(&self) -> RModule { unsafe { RModule::from_rb_value_unchecked(rb_mKernel) } } /// Return Ruby's `Math` module. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "md == Math", md = ruby.module_math()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn module_math(&self) -> RModule { unsafe { RModule::from_rb_value_unchecked(rb_mMath) } } /// Return Ruby's `Process` module. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "md == Process", md = ruby.module_process()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn module_process(&self) -> RModule { unsafe { RModule::from_rb_value_unchecked(rb_mProcess) } } /// Return Ruby's `IO::WaitReadable` module. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "md == IO::WaitReadable", /// md = ruby.module_wait_readable() /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn module_wait_readable(&self) -> RModule { unsafe { RModule::from_rb_value_unchecked(rb_mWaitReadable) } } /// Return Ruby's `IO::WaitWritable` module. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!( /// ruby, /// "md == IO::WaitWritable", /// md = ruby.module_wait_writable() /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn module_wait_writable(&self) -> RModule { unsafe { RModule::from_rb_value_unchecked(rb_mWaitWritable) } } } /// Return Ruby's `Comparable` module. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::module_comparable`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::module_comparable` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn comparable() -> RModule { get_ruby!().module_comparable() } /// Return Ruby's `Enumerable` module. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::module_enumerable`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::module_enumerable` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn enumerable() -> RModule { get_ruby!().module_enumerable() } /// Return Ruby's `Errno` module. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::module_errno`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::module_errno` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn errno() -> RModule { get_ruby!().module_errno() } /// Return Ruby's `FileTest` module. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::module_file_test`] for /// the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::module_file_test` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn file_test() -> RModule { get_ruby!().module_file_test() } /// Return Ruby's `GC` module. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::module_gc`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::module_gc` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn gc() -> RModule { get_ruby!().module_gc() } /// Return Ruby's `Kernel` module. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::module_kernel`] for /// the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::module_kernel` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn kernel() -> RModule { get_ruby!().module_kernel() } /// Return Ruby's `Math` module. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::module_math`] for the /// non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::module_math` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn math() -> RModule { get_ruby!().module_math() } /// Return Ruby's `Process` module. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::module_process`] for /// the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::module_process` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn process() -> RModule { get_ruby!().module_process() } /// Return Ruby's `IO::WaitReadable` module. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::module_wait_readable`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::module_wait_readable` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn wait_readable() -> RModule { get_ruby!().module_wait_readable() } /// Return Ruby's `IO::WaitWritable` module. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::module_wait_writable`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::module_wait_writable` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn wait_writable() -> RModule { get_ruby!().module_wait_writable() } magnus-0.7.1/src/mutex.rs000064400000000000000000000175601046102023000134330ustar 00000000000000//! Types for working with Ruby mutexes. use std::{fmt, time::Duration}; use rb_sys::{ rb_mutex_lock, rb_mutex_locked_p, rb_mutex_new, rb_mutex_sleep, rb_mutex_synchronize, rb_mutex_trylock, rb_mutex_unlock, VALUE, }; use crate::{ class::RClass, error::{protect, Error}, into_value::IntoValue, method::{BlockReturn, Synchronize}, object::Object, r_typed_data::RTypedData, try_convert::TryConvert, value::{ private::{self, ReprValue as _}, ReprValue, Value, }, Ruby, }; /// # `Mutex` /// /// Functions that can be used to create Ruby `Mutex`s. /// /// See also the [`Mutex`] type. impl Ruby { /// Create a Ruby Mutex. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let lock = ruby.mutex_new(); /// assert!(!lock.is_locked()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn mutex_new(&self) -> Mutex { unsafe { Mutex::from_rb_value_unchecked(rb_mutex_new()) } } } /// Wrapper type for a Value known to be an instance of Ruby's Mutex class. /// /// See the [`ReprValue`] and [`Object`] traits for additional methods /// available on this type. See [`Ruby`](Ruby#mutex) for methods to create a /// `Mutex`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct Mutex(RTypedData); impl Mutex { /// Return `Some(Mutex)` if `val` is a `Mutex`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::eval; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(magnus::Mutex::from_value(eval("Mutex.new").unwrap()).is_some()); /// assert!(magnus::Mutex::from_value(eval("true").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { let mutex_class: RClass = Ruby::get_with(val) .class_object() .funcall("const_get", ("Mutex",)) .ok()?; RTypedData::from_value(val) .filter(|_| val.is_kind_of(mutex_class)) .map(Self) } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(RTypedData::from_rb_value_unchecked(val)) } /// Returns whether any threads currently hold the lock. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let lock = ruby.mutex_new(); /// assert!(!lock.is_locked()); /// /// lock.lock()?; /// assert!(lock.is_locked()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn is_locked(self) -> bool { unsafe { Value::new(rb_mutex_locked_p(self.as_rb_value())).to_bool() } } /// Attempts to aquire the lock. /// /// This method does not block. Returns true if the lock can be acquired, /// false otherwise. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let lock = ruby.mutex_new(); /// /// assert!(lock.trylock()); /// assert!(lock.is_locked()); /// /// assert!(!lock.trylock()); /// assert!(lock.is_locked()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn trylock(self) -> bool { unsafe { Value::new(rb_mutex_trylock(self.as_rb_value())).to_bool() } } /// Acquires the lock. /// /// This method will block the current thread until the lock can be /// acquired. Returns `Err` on deadlock. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let lock = ruby.mutex_new(); /// /// lock.lock()?; /// assert!(lock.is_locked()); /// /// assert!(lock.lock().is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn lock(self) -> Result<(), Error> { protect(|| unsafe { Value::new(rb_mutex_lock(self.as_rb_value())) })?; Ok(()) } /// Release the lock. /// /// Returns `Err` if the current thread does not own the lock. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let lock = ruby.mutex_new(); /// lock.lock()?; /// assert!(lock.is_locked()); /// /// lock.unlock()?; /// assert!(!lock.is_locked()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn unlock(self) -> Result<(), Error> { protect(|| unsafe { Value::new(rb_mutex_unlock(self.as_rb_value())) })?; Ok(()) } /// Release the lock for `timeout`, reaquiring it on wakeup. /// /// Returns `Err` if the current thread does not own the lock. /// /// # Examples /// /// ``` /// use std::time::Duration; /// /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let lock = ruby.mutex_new(); /// lock.lock()?; /// lock.sleep(Some(Duration::from_millis(10)))?; /// lock.unlock()?; /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn sleep(self, timeout: Option) -> Result<(), Error> { let ruby = Ruby::get_with(self); protect(|| unsafe { Value::new(rb_mutex_sleep( self.as_rb_value(), ruby.into_value(timeout.map(|d| d.as_secs_f64())) .as_rb_value(), )) })?; Ok(()) } /// Acquires the lock, runs `func`, then releases the lock. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let lock = ruby.mutex_new(); /// let mut i = 0; /// let _: Value = lock.synchronize(|| i += 1)?; /// assert_eq!(1, i); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn synchronize(self, func: F) -> Result where F: FnOnce() -> R, R: BlockReturn, T: TryConvert, { unsafe extern "C" fn call(arg: VALUE) -> VALUE where F: FnOnce() -> R, R: BlockReturn, { let closure = (*(arg as *mut Option)).take().unwrap(); closure.call_handle_error().as_rb_value() } protect(|| unsafe { let mut some_func = Some(func); let closure = &mut some_func as *mut Option as VALUE; Value::new(rb_mutex_synchronize( self.as_rb_value(), Some(call::), closure, )) }) .and_then(TryConvert::try_convert) } } impl fmt::Display for Mutex { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for Mutex { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for Mutex { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.as_value() } } impl Object for Mutex {} unsafe impl private::ReprValue for Mutex {} impl ReprValue for Mutex {} impl TryConvert for Mutex { fn try_convert(val: Value) -> Result { Self::from_value(val).ok_or_else(|| { Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into Mutex", unsafe { val.classname() },), ) }) } } magnus-0.7.1/src/numeric.rs000064400000000000000000000224261046102023000137300ustar 00000000000000//! Types and Traits for working with Ruby’s Numeric class. use std::fmt; use rb_sys::{rb_num_coerce_bin, rb_num_coerce_bit, rb_num_coerce_cmp, rb_num_coerce_relop, VALUE}; use crate::{ error::{protect, Error}, into_value::IntoValue, try_convert::TryConvert, value::{ private::{self, ReprValue as _}, IntoId, NonZeroValue, ReprValue, Value, }, Ruby, }; /// Functions available for all of Ruby's Numeric types. pub trait Numeric: ReprValue + Copy { /// Apply the operator `op` with coercion. /// /// As Ruby's operators are implemented as methods, this function can be /// thought of as a specialised version of [`Value::funcall`], just for /// subclasses of `Numeric`, and that follows Ruby's coercion protocol. /// /// Returns `Ok(U)` if the method returns without error and the return /// value converts to a `U`, or returns Err if the method raises or the /// conversion fails. /// /// The returned errors are tailored for binary operators such as `+`, `/`, /// etc. /// /// # Examples /// /// ``` /// use magnus::{Error, Numeric, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = ruby.integer_from_i64(2); /// let b = ruby.integer_from_i64(3); /// let c: i64 = a.coerce_bin(b, "+")?; /// assert_eq!(c, 5); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// Avoiding type conversion of the result to demonstrate Ruby is coercing /// the types: /// /// ``` /// use magnus::{Error, Float, Numeric, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = ruby.integer_from_i64(2); /// let b = ruby.float_from_f64(3.5); /// let c: Value = a.coerce_bin(b, "+")?; /// let c = Float::from_value(c); /// assert!(c.is_some()); /// assert_eq!(c.unwrap().to_f64(), 5.5); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn coerce_bin(self, other: T, op: ID) -> Result where T: Numeric, ID: IntoId, U: TryConvert, { let op = op.into_id_with(&Ruby::get_with(self)); protect(|| unsafe { Value::new(rb_num_coerce_bin( self.as_rb_value(), other.as_rb_value(), op.as_rb_id(), )) }) .and_then(TryConvert::try_convert) } /// Apply the operator `op` with coercion. /// /// As Ruby's operators are implemented as methods, this function can be /// thought of as a specialised version of [`Value::funcall`], just for /// subclasses of `Numeric`, and that follows Ruby's coercion protocol. /// /// Returns `Ok(U)` if the method returns without error and the return /// value converts to a `U`, or returns Err if the method raises or the /// conversion fails. /// /// The returned errors are tailored for comparison operators such as `<=>`. /// /// Note, if coercion fails this will return `nil`, if you want to detect /// this you should set the result type to `Option`. Other errors in /// applying `op` will still result in an `Err`. /// /// # Examples /// /// ``` /// use std::num::NonZeroI64; /// /// use magnus::{Error, Numeric, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = ruby.rational_new(1, NonZeroI64::new(4).unwrap()); /// let b = ruby.float_from_f64(0.3); /// let result: i64 = a.coerce_cmp(b, "<=>")?; /// assert_eq!(result, -1); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn coerce_cmp(self, other: T, op: ID) -> Result where T: Numeric, ID: IntoId, U: TryConvert, { let op = op.into_id_with(&Ruby::get_with(self)); protect(|| unsafe { Value::new(rb_num_coerce_cmp( self.as_rb_value(), other.as_rb_value(), op.as_rb_id(), )) }) .and_then(TryConvert::try_convert) } /// Apply the operator `op` with coercion. /// /// As Ruby's operators are implemented as methods, this function can be /// thought of as a specialised version of [`Value::funcall`], just for /// subclasses of `Numeric`, and that follows Ruby's coercion protocol. /// /// Returns `Ok(U)` if the method returns without error and the return /// value converts to a `U`, or returns Err if the method raises or the /// conversion fails. /// /// The returned errors are tailored for relationship operators such as /// `<=`. /// /// # Examples /// /// ``` /// use std::num::NonZeroI64; /// /// use magnus::{Error, Numeric, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = ruby.float_from_f64(0.3); /// let b = ruby.rational_new(1, NonZeroI64::new(4).unwrap()); /// let result: bool = a.coerce_cmp(b, "<=")?; /// assert_eq!(result, false); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn coerce_relop(self, other: T, op: ID) -> Result where T: Numeric, ID: IntoId, U: TryConvert, { let op = op.into_id_with(&Ruby::get_with(self)); protect(|| unsafe { Value::new(rb_num_coerce_relop( self.as_rb_value(), other.as_rb_value(), op.as_rb_id(), )) }) .and_then(TryConvert::try_convert) } /// Apply the operator `op` with coercion. /// /// As Ruby's operators are implemented as methods, this function can be /// thought of as a specialised version of [`Value::funcall`], just for /// subclasses of `Numeric`, and that follows Ruby's coercion protocol. /// /// Returns `Ok(U)` if the method returns without error and the return /// value converts to a `U`, or returns Err if the method raises or the /// conversion fails. /// /// The returned errors are tailored for bitwise operators such as `|`, /// `^`, etc. /// /// # Examples /// /// ``` /// use magnus::{Error, Numeric, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = ruby.integer_from_i64(0b00000011); /// let b = ruby.integer_from_i64(0b00001110); /// let result: i64 = a.coerce_cmp(b, "^")?; /// assert_eq!(result, 0b00001101); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn coerce_bit(self, other: T, op: ID) -> Result where T: Numeric, ID: IntoId, U: TryConvert, { let op = op.into_id_with(&Ruby::get_with(self)); protect(|| unsafe { Value::new(rb_num_coerce_bit( self.as_rb_value(), other.as_rb_value(), op.as_rb_id(), )) }) .and_then(TryConvert::try_convert) } } /// Wrapper type for a Value known to be an instance of Ruby’s Numeric class. /// /// See the [`ReprValue`] trait for additional methods available on this type. /// /// # Examples /// /// ``` /// use std::num::NonZeroI64; /// /// use magnus::{numeric::NumericValue, prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = ruby.integer_from_i64(1); /// let b = ruby.rational_new(1, NonZeroI64::new(2).unwrap()); /// let c = ruby.float_from_f64(0.3); /// let d = ruby.integer_from_i64(4); /// /// let result: NumericValue = a.coerce_bin(b, "+")?; /// let result: NumericValue = result.coerce_bin(c, "+")?; /// let result: NumericValue = result.coerce_bin(d, "+")?; /// assert_eq!(f64::try_convert(result.as_value())?, 5.8); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[derive(Clone, Copy)] #[repr(transparent)] pub struct NumericValue(NonZeroValue); impl NumericValue { #[inline] unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } } impl fmt::Display for NumericValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for NumericValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for NumericValue { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } unsafe impl private::ReprValue for NumericValue {} impl Numeric for NumericValue {} impl ReprValue for NumericValue {} impl TryConvert for NumericValue { fn try_convert(val: Value) -> Result { let handle = Ruby::get_with(val); val.is_kind_of(handle.class_numeric()) .then(|| unsafe { Self::from_rb_value_unchecked(val.as_rb_value()) }) .ok_or_else(|| { Error::new( handle.exception_type_error(), format!("no implicit conversion of {} into Numeric", unsafe { val.classname() },), ) }) } } magnus-0.7.1/src/object.rs000064400000000000000000000154561046102023000135410ustar 00000000000000use std::{ffi::CString, mem::transmute}; use rb_sys::{ rb_define_singleton_method, rb_extend_object, rb_ivar_get, rb_ivar_set, rb_singleton_class, }; use crate::{ class::RClass, error::{protect, Error}, into_value::IntoValue, method::Method, module::RModule, try_convert::TryConvert, value::{private::ReprValue as _, IntoId, ReprValue, Value}, Ruby, }; /// Functions available all non-immediate values. pub trait Object: ReprValue + Copy { /// Define a singleton method in `self`'s scope. /// /// Singleton methods defined on a class are Ruby's method for implementing /// 'class' methods. /// /// # Examples /// /// ``` /// use magnus::{function, prelude::*, rb_assert, Error, Ruby}; /// /// fn test() -> i64 { /// 42 /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let module = ruby.define_module("Example")?; /// module.define_singleton_method("test", function!(test, 0))?; /// rb_assert!(ruby, "Example.test == 42"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{function, prelude::*, rb_assert, Error, Ruby}; /// /// #[magnus::wrap(class = "Point", free_immediately, size)] /// struct Point { /// x: isize, /// y: isize, /// } /// /// impl Point { /// fn new(x: isize, y: isize) -> Self { /// Self { x, y } /// } /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let class = ruby.define_class("Point", ruby.class_object())?; /// class.define_singleton_method("new", function!(Point::new, 2))?; /// /// rb_assert!(ruby, "Point.new(1, 2).is_a?(Point)"); /// /// Ok(()) /// } /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// # Ruby::init(example).unwrap() /// ``` fn define_singleton_method(self, name: &str, func: M) -> Result<(), Error> where M: Method, { debug_assert_value!(self); let name = CString::new(name).unwrap(); protect(|| { unsafe { rb_define_singleton_method( self.as_rb_value(), name.as_ptr(), transmute(func.as_ptr()), M::arity().into(), ) }; Ruby::get_with(self).qnil() })?; Ok(()) } /// Get the value for the instance variable `name` within `self`'s scope. /// /// Note, the `@` is part of the name. An instance variable can be set and /// retrieved without a preceding `@` and it will work, but the instance /// variable will be invisible to Ruby code. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, RObject, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let val: RObject = ruby.eval( /// r#" /// class Example /// def initialize(value) /// @value = value /// end /// end /// Example.new("foo") /// "#, /// )?; /// /// assert_eq!(val.ivar_get::<_, String>("@value")?, "foo"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn ivar_get(self, name: T) -> Result where T: IntoId, U: TryConvert, { debug_assert_value!(self); let id = name.into_id_with(&Ruby::get_with(self)); let res = unsafe { protect(|| Value::new(rb_ivar_get(self.as_rb_value(), id.as_rb_id()))) }; res.and_then(TryConvert::try_convert) } /// Set the value for the instance variable `name` within `self`'s scope. /// /// Note, the `@` is part of the name. Setting an instance variable without /// a preceding `@` will work, but the instance variable will be invisible /// to Ruby code. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, rb_assert, Error, RObject, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let obj: RObject = ruby.eval( /// r#" /// class Example /// def initialize(value) /// @value = value /// end /// /// def value /// @value /// end /// end /// Example.new("foo") /// "#, /// )?; /// /// obj.ivar_set("@value", "bar")?; /// rb_assert!(ruby, r#"obj.value == "bar""#, obj); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn ivar_set(self, name: T, value: U) -> Result<(), Error> where T: IntoId, U: IntoValue, { debug_assert_value!(self); let handle = Ruby::get_with(self); let id = name.into_id_with(&handle); let value = value.into_value_with(&handle); unsafe { protect(|| { Value::new(rb_ivar_set( self.as_rb_value(), id.as_rb_id(), value.as_rb_value(), )) }) }?; Ok(()) } /// Finds or creates the singleton class of `self`. /// /// Returns `Err` if `self` can not have a singleton class. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ruby.str_new("example").singleton_class().is_ok()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn singleton_class(self) -> Result { protect(|| unsafe { RClass::from_rb_value_unchecked(rb_singleton_class(self.as_rb_value())) }) } /// Extend `self` with `module`. /// /// # Examples /// /// ``` /// use magnus::{function, prelude::*, rb_assert, Error, RObject, Ruby}; /// /// fn test() -> i64 { /// 42 /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let module = ruby.module_new(); /// module.define_method("test", function!(test, 0))?; /// /// let obj = RObject::try_convert(ruby.class_object().new_instance(())?)?; /// obj.extend_object(module)?; /// rb_assert!(ruby, "obj.test == 42", obj); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn extend_object(self, module: RModule) -> Result<(), Error> { protect(|| { unsafe { rb_extend_object(self.as_rb_value(), module.as_rb_value()) }; Ruby::get_with(self).qnil() })?; Ok(()) } } magnus-0.7.1/src/process.rs000064400000000000000000000103201046102023000137320ustar 00000000000000//! Types for working with processes. //! //! See also [`Ruby`](Ruby#process) for functions for working with processes. #[cfg(unix)] use std::os::unix::process::ExitStatusExt; #[cfg(windows)] use std::os::windows::process::ExitStatusExt; use std::{num::NonZeroU32, os::raw::c_int, process::ExitStatus, ptr::null}; use rb_sys::{rb_sys_fail, rb_waitpid}; use crate::{ api::Ruby, error::{protect, Error}, }; /// # Process /// /// Functions for working with processes. impl Ruby { /// Wait for a process. /// /// This function releases Ruby's Global VM Lock (GVL), so while it will /// block the current thread, other Ruby threads can be scheduled. /// /// Returns the Process ID (PID) of the process waited, and its exit status. /// /// If the `NOHANG` flag is passed this function will not block, instead it /// will clean up an exited child process if there is one, or returns /// `None` if there is no exited child process. /// /// If the `UNTRACED` flag is passed, this function will also return /// stopped processes (e.g. that can be resumed), not only exited processes. /// For these stopped processes the exit status will be reported as /// successful, although they have not yet exited. /// /// # Examples /// /// ``` /// use std::process::Command; /// /// use magnus::{process::WaitTarget, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let child = Command::new("ls").spawn().unwrap(); /// let (pid, status) = ruby /// .waitpid(WaitTarget::ChildPid(child.id()), Default::default())? /// .unwrap(); /// assert_eq!(child.id(), pid.get()); /// assert!(status.success()); /// /// Ok(()) /// } /// # #[cfg(unix)] /// # Ruby::init(example).unwrap() /// ``` pub fn waitpid( &self, pid: WaitTarget, flags: Flags, ) -> Result, Error> { let mut out_pid = 0; let mut status: c_int = 0; protect(|| unsafe { out_pid = rb_waitpid(pid.to_i32() as _, &mut status as *mut c_int, flags.0); if out_pid < 0 { rb_sys_fail(null()); } self.qnil() })?; Ok(NonZeroU32::new(out_pid as u32).map(|pid| (pid, ExitStatus::from_raw(status as _)))) } } #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] const WNOHANG: c_int = 0x00000001; #[cfg(any(target_os = "solaris", target_os = "illumos"))] const WNOHANG: c_int = 0x40; #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] const WUNTRACED: c_int = 0x00000002; #[cfg(any(target_os = "solaris", target_os = "illumos"))] const WUNTRACED: c_int = 0x04; /// Argument type for [`Ruby::waitpid`]. #[derive(Clone, Copy)] pub enum WaitTarget { /// Wait for the given child process ChildPid(u32), /// Wait for any child process with the process group of the current /// process. ProcessGroup, // 0 /// Wait for any child process. AnyChild, // -1 /// Wait for any child process with the given process group. ChildProcessGroup(u32), // negative } impl WaitTarget { fn to_i32(self) -> i32 { match self { Self::ChildPid(pid) => pid as i32, Self::ProcessGroup => 0, Self::AnyChild => -1, Self::ChildProcessGroup(pid) => -(pid as i32), } } } impl Default for WaitTarget { fn default() -> Self { Self::AnyChild } } /// Argument type for [`Ruby::waitpid`]. #[derive(Clone, Copy)] pub struct Flags(c_int); impl Flags { /// An instance of `Flags` with only `NOHANG` set. pub const NOHANG: Self = Self::new().nohang(); /// An instance of `Flags` with only `UNTRACED` set. pub const UNTRACED: Self = Self::new().untraced(); /// Create a new `Flags` with no flags set. pub const fn new() -> Self { Self(0) } /// Set the `NOHANG` flag. pub const fn nohang(self) -> Self { Self(self.0 | WNOHANG) } /// Set the `UNTRACED` flag. pub const fn untraced(self) -> Self { Self(self.0 | WUNTRACED) } } impl Default for Flags { fn default() -> Self { Self::new() } } magnus-0.7.1/src/r_array.rs000064400000000000000000001666731046102023000137420ustar 00000000000000//! Types and functions for working with Ruby’s Array class. use std::{cmp::Ordering, convert::Infallible, fmt, marker::PhantomData, os::raw::c_long, slice}; #[cfg(ruby_gte_3_2)] use rb_sys::rb_ary_hidden_new; #[cfg(ruby_lt_3_2)] use rb_sys::rb_ary_tmp_new as rb_ary_hidden_new; use rb_sys::{ self, rb_ary_assoc, rb_ary_cat, rb_ary_clear, rb_ary_cmp, rb_ary_concat, rb_ary_delete, rb_ary_delete_at, rb_ary_entry, rb_ary_includes, rb_ary_join, rb_ary_new, rb_ary_new_capa, rb_ary_new_from_values, rb_ary_plus, rb_ary_pop, rb_ary_push, rb_ary_rassoc, rb_ary_replace, rb_ary_resize, rb_ary_reverse, rb_ary_rotate, rb_ary_shared_with_p, rb_ary_shift, rb_ary_sort_bang, rb_ary_store, rb_ary_subseq, rb_ary_to_ary, rb_ary_unshift, rb_check_array_type, rb_obj_hide, rb_obj_reveal, ruby_value_type, RARRAY_CONST_PTR, RARRAY_LEN, VALUE, }; use seq_macro::seq; use crate::{ enumerator::Enumerator, error::{protect, Error}, gc, into_value::{IntoValue, IntoValueFromNative}, object::Object, r_string::{IntoRString, RString}, try_convert::{TryConvert, TryConvertOwned}, value::{ private::{self, ReprValue as _}, NonZeroValue, ReprValue, Value, }, Ruby, }; /// # `RArray` /// /// Functions that can be used to create Ruby `Array`s. /// /// See also the [`RArray`] type. impl Ruby { /// Create a new empty `RArray`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_new(); /// assert!(ary.is_empty()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn ary_new(&self) -> RArray { unsafe { RArray::from_rb_value_unchecked(rb_ary_new()) } } /// Create a new empty `RArray` with capacity for `n` elements /// pre-allocated. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_new_capa(16); /// assert!(ary.is_empty()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn ary_new_capa(&self, n: usize) -> RArray { unsafe { RArray::from_rb_value_unchecked(rb_ary_new_capa(n as c_long)) } } /// Create a new `RArray` from a Rust vector. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_from_vec(vec![1, 2, 3]); /// rb_assert!(ruby, "ary == [1, 2, 3]", ary); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn ary_from_vec(&self, vec: Vec) -> RArray where T: IntoValueFromNative, { self.ary_from_iter(vec) } /// Create a new `RArray` containing the elements in `slice`. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_new_from_values(&[ /// ruby.to_symbol("a").as_value(), /// ruby.integer_from_i64(1).as_value(), /// ruby.qnil().as_value(), /// ]); /// rb_assert!(ruby, "ary == [:a, 1, nil]", ary); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_new_from_values(&[ /// ruby.to_symbol("a"), /// ruby.to_symbol("b"), /// ruby.to_symbol("c"), /// ]); /// rb_assert!(ruby, "ary == [:a, :b, :c]", ary); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn ary_new_from_values(&self, slice: &[T]) -> RArray where T: ReprValue, { let ptr = slice.as_ptr() as *const VALUE; unsafe { RArray::from_rb_value_unchecked(rb_ary_new_from_values(slice.len() as c_long, ptr)) } } /// Create a new `RArray` from a Rust iterator. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_from_iter((1..4).map(|i| i * 10)); /// rb_assert!(ruby, "ary == [10, 20, 30]", ary); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn ary_from_iter(&self, iter: I) -> RArray where I: IntoIterator, T: IntoValue, { self.ary_try_from_iter(iter.into_iter().map(Result::<_, Infallible>::Ok)) .unwrap() } /// Create a new `RArray` from a fallible Rust iterator. /// /// Returns `Ok(RArray)` on sucess or `Err(E)` with the first error /// encountered. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby /// .ary_try_from_iter("1,2,3,4".split(',').map(|s| s.parse::())) /// .map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?; /// rb_assert!(ruby, "ary == [1, 2, 3, 4]", ary); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let err = ruby /// .ary_try_from_iter("1,2,foo,4".split(',').map(|s| s.parse::())) /// .unwrap_err(); /// assert_eq!(err.to_string(), "invalid digit found in string"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn ary_try_from_iter(&self, iter: I) -> Result where I: IntoIterator>, T: IntoValue, { let iter = iter.into_iter(); let (lower, _) = iter.size_hint(); let ary = if lower > 0 { self.ary_new_capa(lower) } else { self.ary_new() }; let mut buffer = [self.qnil().as_value(); 128]; let mut i = 0; for v in iter { buffer[i] = self.into_value(v?); i += 1; if i >= buffer.len() { i = 0; ary.cat(&buffer).unwrap(); } } ary.cat(&buffer[..i]).unwrap(); Ok(ary) } /// Create a new Ruby Array that may only contain elements of type `T`. /// /// On creation this Array is hidden from Ruby, and must be consumed to /// pass it to Ruby (where it reverts to a regular untyped Array). It is /// then inaccessible to Rust. /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.typed_ary_new::(); /// ary.push("1".parse().unwrap())?; /// ary.push("2.3".parse().unwrap())?; /// ary.push("4.5".parse().unwrap())?; /// rb_assert!(ruby, "ary == [1.0, 2.3, 4.5]", ary); /// // ary has moved and can no longer be used. /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn typed_ary_new(&self) -> TypedArray { unsafe { let ary = rb_ary_hidden_new(0); TypedArray(NonZeroValue::new_unchecked(Value::new(ary)), PhantomData) } } } /// A Value pointer to a RArray struct, Ruby's internal representation of an /// Array. /// /// See the [`ReprValue`] and [`Object`] traits for additional methods /// available on this type. See [`Ruby`](Ruby#rarray) for methods to create an /// `RArray`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct RArray(NonZeroValue); impl RArray { /// Return `Some(RArray)` if `val` is a `RArray`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, RArray}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(RArray::from_value(eval(r#"[true, 0, "example"]"#).unwrap()).is_some()); /// assert!(RArray::from_value(eval(r#"{"answer" => 42}"#).unwrap()).is_none()); /// assert!(RArray::from_value(eval(r"nil").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { (val.rb_type() == ruby_value_type::RUBY_T_ARRAY) .then(|| Self(NonZeroValue::new_unchecked(val))) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } /// Create a new empty `RArray`. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::ary_new`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::RArray; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let ary = RArray::new(); /// assert!(ary.is_empty()); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::ary_new` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn new() -> Self { get_ruby!().ary_new() } /// Create a new empty `RArray` with capacity for `n` elements /// pre-allocated. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::ary_new_capa`] for /// the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::RArray; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let ary = RArray::with_capacity(16); /// assert!(ary.is_empty()); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::ary_new_capa` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn with_capacity(n: usize) -> Self { get_ruby!().ary_new_capa(n) } /// Convert or wrap a Ruby [`Value`] to a `RArray`. /// /// If `val` responds to `#to_ary` calls that and passes on the returned /// array, otherwise returns a single element array containing `val`. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, rb_assert, Error, RArray, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = RArray::to_ary(ruby.integer_from_i64(1).as_value())?; /// rb_assert!(ruby, "[1] == ary", ary); /// /// let ary = RArray::to_ary(ruby.ary_from_vec(vec![1, 2, 3]).as_value())?; /// rb_assert!(ruby, "[1, 2, 3] == ary", ary); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// This can fail in the case of a misbehaving `#to_ary` method: /// /// ``` /// use magnus::{Error, RArray, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let val = ruby.eval( /// r#" /// o = Object.new /// def o.to_ary /// "not an array" /// end /// o /// "#, /// )?; /// assert!(RArray::to_ary(val).is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn to_ary(val: Value) -> Result { protect(|| unsafe { Self::from_rb_value_unchecked(rb_ary_to_ary(val.as_rb_value())) }) } /// Iterates though `self` and checks each element is convertable to a `T`. /// /// Returns a typed copy of `self`. Mutating the returned copy will not /// mutate `self`. /// /// This makes most sense when `T` is a Ruby type, although that is not /// enforced. If `T` is a Rust type then see [`RArray::to_vec`] for an /// alternative. /// /// # Examples /// /// ``` /// use magnus::{function, prelude::*, typed_data, Error, RArray, Ruby}; /// /// #[magnus::wrap(class = "Point")] /// struct Point { /// x: isize, /// y: isize, /// } /// /// impl Point { /// fn new(x: isize, y: isize) -> Self { /// Self { x, y } /// } /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let point_class = ruby.define_class("Point", ruby.class_object())?; /// point_class.define_singleton_method("new", function!(Point::new, 2))?; /// /// let ary: RArray = ruby.eval( /// r#" /// [ /// Point.new(1, 2), /// Point.new(3, 4), /// Point.new(5, 6), /// ] /// "#, /// )?; /// /// let typed = ary.typecheck::>()?; /// let point = typed.pop()?; /// assert_eq!(point.x, 5); /// assert_eq!(point.y, 6); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap(); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` pub fn typecheck(self) -> Result, Error> where T: TryConvert, { for r in self { T::try_convert(r)?; } unsafe { let ary = rb_ary_hidden_new(0); rb_ary_replace(ary, self.as_rb_value()); Ok(TypedArray( NonZeroValue::new_unchecked(Value::new(ary)), PhantomData, )) } } /// Create a new `RArray` that is a duplicate of `self`. /// /// The new array is only a shallow clone. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = ruby.ary_from_vec(vec![1, 2, 3]); /// let b = a.dup(); /// rb_assert!(ruby, "a == b", a, b); /// a.push(4)?; /// b.push(5)?; /// rb_assert!(ruby, "a == [1, 2, 3, 4]", a); /// rb_assert!(ruby, "b == [1, 2, 3, 5]", b); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn dup(self) -> Self { // rb_ary_subseq does a cheap copy-on-write unsafe { Self::from_rb_value_unchecked(rb_ary_subseq(self.as_rb_value(), 0, c_long::MAX)) } } /// Return the number of entries in `self` as a Rust [`usize`]. /// /// # Examples /// /// ``` /// use magnus::{Error, RArray, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_new(); /// assert_eq!(ary.len(), 0); /// /// let ary: RArray = ruby.eval("[:a, :b, :c]")?; /// assert_eq!(ary.len(), 3); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn len(self) -> usize { debug_assert_value!(self); unsafe { RARRAY_LEN(self.as_rb_value()) as _ } } /// Return whether self contains any entries or not. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_new(); /// assert!(ary.is_empty()); /// /// ary.push("foo")?; /// assert!(!ary.is_empty()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn is_empty(self) -> bool { self.len() == 0 } /// Returns `true` if `val` is in `self`, `false` otherwise. /// /// # Examples /// /// ``` /// use magnus::{Error, RArray, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary: RArray = ruby.eval(r#"[:foo, "bar", 2]"#)?; /// assert!(ary.includes(ruby.to_symbol("foo"))); /// assert!(ary.includes("bar")); /// assert!(ary.includes(2)); /// // 2.0 == 2 in Ruby /// assert!(ary.includes(2.0)); /// assert!(!ary.includes("foo")); /// assert!(!ary.includes(ruby.qnil())); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn includes(self, val: T) -> bool where T: IntoValue, { let val = Ruby::get_with(self).into_value(val); unsafe { Value::new(rb_ary_includes(self.as_rb_value(), val.as_rb_value())).to_bool() } } /// Concatenate elements from the slice `s` to `self`. /// /// Returns `Err` if `self` is frozen. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_new(); /// ary.cat(&[ /// ruby.to_symbol("a").as_value(), /// ruby.integer_from_i64(1).as_value(), /// ruby.qnil().as_value(), /// ])?; /// rb_assert!(ruby, "ary == [:a, 1, nil]", ary); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_new(); /// ary.cat(&[ /// ruby.to_symbol("a"), /// ruby.to_symbol("b"), /// ruby.to_symbol("c"), /// ])?; /// rb_assert!(ruby, "ary == [:a, :b, :c]", ary); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn cat(self, s: &[T]) -> Result<(), Error> where T: ReprValue, { let ptr = s.as_ptr() as *const VALUE; protect(|| unsafe { Value::new(rb_ary_cat(self.as_rb_value(), ptr, s.len() as c_long)) })?; Ok(()) } /// Concatenate elements from Ruby array `other` to `self`. /// /// Returns `Err` if `self` is frozen. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = ruby.ary_from_vec(vec![1, 2, 3]); /// let b = ruby.ary_from_vec(vec!["a", "b", "c"]); /// a.concat(b)?; /// rb_assert!(ruby, r#"a == [1, 2, 3, "a", "b", "c"]"#, a); /// rb_assert!(ruby, r#"b == ["a", "b", "c"]"#, b); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn concat(self, other: Self) -> Result<(), Error> { protect(|| unsafe { Value::new(rb_ary_concat(self.as_rb_value(), other.as_rb_value())) })?; Ok(()) } /// Create a new `RArray` containing the both the elements in `self` and /// `other`. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = ruby.ary_from_vec(vec![1, 2, 3]); /// let b = ruby.ary_from_vec(vec!["a", "b", "c"]); /// let c = a.plus(b); /// rb_assert!(ruby, r#"c == [1, 2, 3, "a", "b", "c"]"#, c); /// rb_assert!(ruby, r#"a == [1, 2, 3]"#, a); /// rb_assert!(ruby, r#"b == ["a", "b", "c"]"#, b); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn plus(self, other: Self) -> Self { unsafe { Self::from_rb_value_unchecked(rb_ary_plus(self.as_rb_value(), other.as_rb_value())) } } /// Create a new `RArray` containing the elements in `slice`. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::ary_new_from_values`] for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{prelude::*, rb_assert, value::qnil, Integer, RArray, Symbol}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let ary = RArray::from_slice(&[ /// Symbol::new("a").as_value(), /// Integer::from_i64(1).as_value(), /// qnil().as_value(), /// ]); /// rb_assert!("ary == [:a, 1, nil]", ary); /// ``` /// /// ``` /// # #![allow(deprecated)] /// use magnus::{rb_assert, RArray, Symbol}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let ary = RArray::from_slice(&[Symbol::new("a"), Symbol::new("b"), Symbol::new("c")]); /// rb_assert!("ary == [:a, :b, :c]", ary); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::ary_new_from_values` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn from_slice(slice: &[T]) -> Self where T: ReprValue, { get_ruby!().ary_new_from_values(slice) } /// Add `item` to the end of `self`. /// /// Returns `Err` if `self` is frozen. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_new(); /// ary.push(ruby.to_symbol("a"))?; /// ary.push(1)?; /// ary.push(())?; /// rb_assert!(ruby, "ary == [:a, 1, nil]", ary); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn push(self, item: T) -> Result<(), Error> where T: IntoValue, { let item = Ruby::get_with(self).into_value(item); protect(|| unsafe { Value::new(rb_ary_push(self.as_rb_value(), item.as_rb_value())) })?; Ok(()) } /// Remove and return the last element of `self`, converting it to a `T`. /// /// Errors if `self` is frozen or if the conversion fails. /// /// # Examples /// /// ``` /// use magnus::{Error, RArray, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary: RArray = ruby.eval("[1, 2, 3]")?; /// assert_eq!(ary.pop::()?, 3); /// assert_eq!(ary.pop::()?, 2); /// assert_eq!(ary.pop::()?, 1); /// assert!(ary.pop::().is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{Error, RArray, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary: RArray = ruby.eval("[1, 2, 3]")?; /// assert_eq!(ary.pop::>()?, Some(3)); /// assert_eq!(ary.pop::>()?, Some(2)); /// assert_eq!(ary.pop::>()?, Some(1)); /// assert_eq!(ary.pop::>()?, None); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn pop(self) -> Result where T: TryConvert, { protect(|| unsafe { Value::new(rb_ary_pop(self.as_rb_value())) }) .and_then(TryConvert::try_convert) } /// Add `item` to the beginning of `self`. /// /// Returns `Err` if `self` is frozen. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_new(); /// ary.unshift(ruby.to_symbol("a"))?; /// ary.unshift(1)?; /// ary.unshift(())?; /// rb_assert!(ruby, "ary == [nil, 1, :a]", ary); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn unshift(self, item: T) -> Result<(), Error> where T: IntoValue, { let item = Ruby::get_with(self).into_value(item); protect(|| unsafe { Value::new(rb_ary_unshift(self.as_rb_value(), item.as_rb_value())) })?; Ok(()) } /// Remove and return the first element of `self`, converting it to a `T`. /// /// Errors if `self` is frozen or if the conversion fails. /// /// # Examples /// /// ``` /// use magnus::{Error, RArray, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary: RArray = ruby.eval("[1, 2, 3]")?; /// assert_eq!(ary.shift::()?, 1); /// assert_eq!(ary.shift::()?, 2); /// assert_eq!(ary.shift::()?, 3); /// assert!(ary.shift::().is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{Error, RArray, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary: RArray = ruby.eval("[1, 2, 3]")?; /// assert_eq!(ary.shift::>()?, Some(1)); /// assert_eq!(ary.shift::>()?, Some(2)); /// assert_eq!(ary.shift::>()?, Some(3)); /// assert_eq!(ary.shift::>()?, None); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn shift(self) -> Result where T: TryConvert, { protect(|| unsafe { Value::new(rb_ary_shift(self.as_rb_value())) }) .and_then(TryConvert::try_convert) } /// Remove all elements from `self` that match `item`'s `==` method. /// /// Returns `Err` if `self` is frozen. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_from_vec(vec![1, 1, 2, 3]); /// ary.delete(1)?; /// rb_assert!(ruby, "ary == [2, 3]", ary); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn delete(self, item: T) -> Result<(), Error> where T: IntoValue, { let item = Ruby::get_with(self).into_value(item); protect(|| unsafe { Value::new(rb_ary_delete(self.as_rb_value(), item.as_rb_value())) })?; Ok(()) } /// Remove and return the element of `self` at `index`, converting it to a /// `T`. /// /// `index` may be negative, in which case it counts backward from the end /// of the array. /// /// Returns `Err` if `self` is frozen or if the conversion fails. /// /// The returned element will be Ruby's `nil` when `index` is out of bounds /// this makes it impossible to distingush between out of bounds and /// removing `nil` without an additional length check. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_from_vec(vec!["a", "b", "c"]); /// let removed: Option = ary.delete_at(1)?; /// assert_eq!(removed, Some(String::from("b"))); /// rb_assert!(ruby, r#"ary == ["a", "c"]"#, ary); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn delete_at(self, index: isize) -> Result where T: TryConvert, { protect(|| unsafe { Value::new(rb_ary_delete_at(self.as_rb_value(), index as c_long)) }) .and_then(TryConvert::try_convert) } /// Remove all elements from `self` /// /// Returns `Err` if `self` is frozen. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_from_vec::(vec![1, 2, 3]); /// assert!(!ary.is_empty()); /// ary.clear()?; /// assert!(ary.is_empty()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn clear(self) -> Result<(), Error> { protect(|| unsafe { Value::new(rb_ary_clear(self.as_rb_value())) })?; Ok(()) } /// Expand or shrink the length of `self`. /// /// When increasing the length of the array empty positions will be filled /// with `nil`. /// /// Returns `Err` if `self` is frozen. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_from_vec::(vec![1, 2, 3]); /// ary.resize(5)?; /// rb_assert!(ruby, "ary == [1, 2, 3, nil, nil]", ary); /// ary.resize(2)?; /// rb_assert!(ruby, "ary == [1, 2]", ary); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn resize(self, len: usize) -> Result<(), Error> { protect(|| unsafe { Value::new(rb_ary_resize(self.as_rb_value(), len as c_long)) })?; Ok(()) } /// Reverses the order of `self` in place. /// /// Returns `Err` if `self` is frozen. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_from_vec::(vec![1, 2, 3]); /// ary.reverse()?; /// rb_assert!(ruby, "ary == [3, 2, 1]", ary); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn reverse(self) -> Result<(), Error> { protect(|| unsafe { Value::new(rb_ary_reverse(self.as_rb_value())) })?; Ok(()) } /// Rotates the elements of `self` in place by `rot` positions. /// /// If `rot` is positive elements are rotated to the left, if negative, /// to the right. /// /// Returns `Err` if `self` is frozen. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_from_vec::(vec![1, 2, 3, 4, 5, 6, 7]); /// ary.rotate(3)?; /// rb_assert!(ruby, "ary == [4, 5, 6, 7, 1, 2, 3]", ary); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_from_vec::(vec![1, 2, 3, 4, 5, 6, 7]); /// ary.rotate(-3)?; /// rb_assert!(ruby, "ary == [5, 6, 7, 1, 2, 3, 4]", ary); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn rotate(self, rot: isize) -> Result<(), Error> { protect(|| unsafe { Value::new(rb_ary_rotate(self.as_rb_value(), rot as c_long)) })?; Ok(()) } /// Storts the elements of `self` in place using Ruby's `<=>` operator. /// /// Returns `Err` if `self` is frozen. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_from_vec::(vec![2, 1, 3]); /// ary.sort()?; /// rb_assert!(ruby, "ary == [1, 2, 3]", ary); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn sort(self) -> Result<(), Error> { protect(|| unsafe { Value::new(rb_ary_sort_bang(self.as_rb_value())) })?; Ok(()) } /// Create a new `RArray` from a Rust vector. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::ary_from_vec`] for /// the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{rb_assert, RArray}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let ary = RArray::from_vec(vec![1, 2, 3]); /// rb_assert!("ary == [1, 2, 3]", ary); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::ary_from_vec` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn from_vec(vec: Vec) -> Self where T: IntoValueFromNative, { get_ruby!().ary_from_vec(vec) } /// Return `self` as a slice of [`Value`]s. /// /// # Safety /// /// This is directly viewing memory owned and managed by Ruby. Ruby may /// modify or free the memory backing the returned slice, the caller must /// ensure this does not happen. /// /// Ruby must not be allowed to garbage collect or modify `self` while a /// refrence to the slice is held. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, RArray, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary: RArray = ruby.eval("[1, 2, 3, 4, 5]")?; /// // must not call any Ruby api that may modify ary while we have a /// // refrence to the return value of ::from_slice() /// unsafe { /// let middle = ruby.ary_new_from_values(&ary.as_slice()[1..4]); /// rb_assert!(ruby, "middle == [2, 3, 4]", middle); /// } /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub unsafe fn as_slice(&self) -> &[Value] { self.as_slice_unconstrained() } pub(crate) unsafe fn as_slice_unconstrained<'a>(self) -> &'a [Value] { debug_assert_value!(self); slice::from_raw_parts( RARRAY_CONST_PTR(self.as_rb_value()) as *const Value, RARRAY_LEN(self.as_rb_value()) as usize, ) } /// Convert `self` to a Rust vector of `T`s. Errors if converting any /// element in the array fails. /// /// This will only convert to a map of 'owned' Rust native types. The types /// representing Ruby objects can not be stored in a heap-allocated /// datastructure like a [`Vec`] as they are hidden from the mark phase /// of Ruby's garbage collector, and thus may be prematurely garbage /// collected in the following sweep phase. /// /// # Examples /// /// ``` /// use magnus::{Error, RArray, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary: RArray = ruby.eval("[1, 2, 3]")?; /// assert_eq!(ary.to_vec::()?, vec![1, 2, 3]); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn to_vec(self) -> Result, Error> where T: TryConvertOwned, { unsafe { self.as_slice().iter().map(|v| T::try_convert(*v)).collect() } } /// Convert `self` to a Rust array of [`Value`]s, of length `N`. /// /// Errors if the Ruby array is not of length `N`. /// /// # Examples /// /// ``` /// use magnus::{Error, RArray, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary: RArray = ruby.eval("[1, 2, 3]")?; /// assert!(ary.to_value_array::<3>().is_ok()); /// assert!(ary.to_value_array::<2>().is_err()); /// assert!(ary.to_value_array::<4>().is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn to_value_array(self) -> Result<[Value; N], Error> { unsafe { self.as_slice().try_into().map_err(|_| { Error::new( Ruby::get_with(self).exception_type_error(), format!("expected Array of length {}", N), ) }) } } /// Convert `self` to a Rust array of `T`s, of length `N`. /// /// Errors if converting any element in the array fails, or if the Ruby /// array is not of length `N`. /// /// # Examples /// /// ``` /// use magnus::{Error, RArray, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary: RArray = ruby.eval("[1, 2, 3]")?; /// assert_eq!(ary.to_array::()?, [1, 2, 3]); /// assert!(ary.to_array::().is_err()); /// assert!(ary.to_array::().is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn to_array(self) -> Result<[T; N], Error> where T: TryConvert, { unsafe { let slice = self.as_slice(); if slice.len() != N { return Err(Error::new( Ruby::get_with(self).exception_type_error(), format!("expected Array of length {}", N), )); } // one day might be able to collect direct into an array, but for // now need to go via Vec slice .iter() .copied() .map(TryConvert::try_convert) .collect::, Error>>() .map(|v| v.try_into().ok().unwrap()) } } /// Stringify the contents of `self` and join the sequence with `sep`. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_new_from_values(&[ /// ruby.to_symbol("a").as_value(), /// ruby.integer_from_i64(1).as_value(), /// ruby.qnil().as_value(), /// ]); /// assert_eq!(ary.join(", ").unwrap().to_string().unwrap(), "a, 1, "); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn join(self, sep: T) -> Result where T: IntoRString, { let sep = sep.into_r_string_with(&Ruby::get_with(self)); protect(|| unsafe { RString::from_rb_value_unchecked(rb_ary_join(self.as_rb_value(), sep.as_rb_value())) }) } /// Return the element at `offset`, converting it to a `T`. /// /// Errors if the conversion fails. /// /// An offset out of range will return `nil`. /// /// # Examples /// /// ``` /// use magnus::{Error, RArray, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary: RArray = ruby.eval(r#"["a", "b", "c"]"#)?; /// /// assert_eq!(ary.entry::(0)?, String::from("a")); /// assert_eq!(ary.entry::(0)?, 'a'); /// assert_eq!(ary.entry::>(0)?, Some(String::from("a"))); /// assert_eq!(ary.entry::(1)?, String::from("b")); /// assert_eq!(ary.entry::(-1)?, String::from("c")); /// assert_eq!(ary.entry::>(3)?, None); /// /// assert!(ary.entry::(0).is_err()); /// assert!(ary.entry::(3).is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn entry(self, offset: isize) -> Result where T: TryConvert, { unsafe { T::try_convert(Value::new(rb_ary_entry( self.as_rb_value(), offset as c_long, ))) } } /// Set the element at `offset`. /// /// If `offset` is beyond the current size of the array the array will be /// expanded and padded with `nil`. /// /// Returns `Err` if `self` is frozen. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_new_from_values(&[ /// ruby.to_symbol("a"), /// ruby.to_symbol("b"), /// ruby.to_symbol("c"), /// ]); /// ary.store(0, ruby.to_symbol("d"))?; /// ary.store(5, ruby.to_symbol("e"))?; /// ary.store(6, ruby.to_symbol("f"))?; /// ary.store(-1, ruby.to_symbol("g"))?; /// rb_assert!(ruby, "ary == [:d, :b, :c, nil, nil, :e, :g]", ary); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn store(self, offset: isize, val: T) -> Result<(), Error> where T: IntoValue, { let handle = Ruby::get_with(self); let val = handle.into_value(val); protect(|| { unsafe { rb_ary_store(self.as_rb_value(), offset as c_long, val.as_rb_value()) }; handle.qnil() })?; Ok(()) } /// Returns an [`Enumerator`] over `self`. /// /// # Examples /// /// ``` /// use magnus::{eval, prelude::*, RArray}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let mut res = Vec::new(); /// # #[allow(deprecated)] /// for i in eval::("[1, 2, 3]").unwrap().each() { /// res.push(i64::try_convert(i.unwrap()).unwrap()); /// } /// assert_eq!(res, vec![1, 2, 3]); /// ``` #[deprecated( since = "0.7.0", note = "Please use `ary.into_iter()` or `ary.enumeratorize(\"each\", ())` instead." )] pub fn each(self) -> Enumerator { // TODO why doesn't rb_ary_each work? self.enumeratorize("each", ()) } /// Returns true if both `self` and `other` share the same backing storage. /// /// It is possible for two Ruby Arrays to share the same backing storage, /// and only when one of them is modified will the copy-on-write cost be /// paid. /// /// Currently, this method will only return `true` if `self` and `other` /// are of the same length, even though Ruby may continue to use the same /// backing storage after popping a value from either of the arrays. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_from_vec((0..256).collect()); /// let copy = ruby.ary_new(); /// copy.replace(ary)?; /// assert!(ary.is_shared(copy)); /// assert!(copy.is_shared(ary)); /// copy.push(11)?; /// assert!(!ary.is_shared(copy)); /// assert!(!copy.is_shared(ary)); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn is_shared(self, other: Self) -> bool { unsafe { Value::new(rb_ary_shared_with_p( self.as_rb_value(), other.as_rb_value(), )) .to_bool() } } /// Replace the contents of `self` with `from`. /// /// `from` is unmodified, and `self` becomes a copy of `from`. `self`'s /// former contents are abandoned. /// /// This is a very cheap operation, `self` will point at `from`'s backing /// storage until one is modified, and only then will the copy-on-write /// cost be paid. /// /// Returns `Err` if `self` is frozen. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_from_vec((0..256).collect()); /// let copy = ruby.ary_new(); /// copy.replace(ary)?; /// assert!(copy.is_shared(ary)); /// copy.push(11)?; /// assert!(!copy.is_shared(ary)); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn replace(self, from: Self) -> Result<(), Error> { protect(|| unsafe { Value::new(rb_ary_replace(self.as_rb_value(), from.as_rb_value())) })?; Ok(()) } /// Create a new array from a subsequence of `self`. /// /// This is a very cheap operation, as `self` and the new array will share /// their backing storage until one is modified. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_from_vec(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); /// let a = ary.subseq(0, 5).unwrap(); /// let b = ary.subseq(5, 5).unwrap(); /// rb_assert!(ruby, "a == [1, 2, 3, 4, 5]", a); /// rb_assert!(ruby, "b == [6, 7, 8, 9, 10]", b); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` // TODO maybe take a range instead of offset and length pub fn subseq(self, offset: usize, length: usize) -> Option { unsafe { let val = Value::new(rb_ary_subseq( self.as_rb_value(), offset as c_long, length as c_long, )); (!val.is_nil()).then(|| Self::from_rb_value_unchecked(val.as_rb_value())) } } /// Search `self` as an 'associative array' for `key`. /// /// Assumes `self` is an array of arrays, searching from the start of the /// outer array, returns the first inner array where the first element /// matches `key`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_from_vec(vec![("foo", 1), ("bar", 2), ("baz", 3), ("baz", 4)]); /// assert_eq!( /// ary.assoc::<_, (String, i64)>("baz")?, /// (String::from("baz"), 3) /// ); /// assert_eq!(ary.assoc::<_, Option<(String, i64)>>("quz")?, None); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn assoc(self, key: K) -> Result where K: IntoValue, T: TryConvert, { let key = Ruby::get_with(self).into_value(key); protect(|| unsafe { Value::new(rb_ary_assoc(self.as_rb_value(), key.as_rb_value())) }) .and_then(TryConvert::try_convert) } /// Search `self` as an 'associative array' for `value`. /// /// Assumes `self` is an array of arrays, searching from the start of the /// outer array, returns the first inner array where the second element /// matches `value`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_from_vec(vec![("foo", 1), ("bar", 2), ("baz", 3), ("qux", 3)]); /// assert_eq!(ary.rassoc::<_, (String, i64)>(3)?, (String::from("baz"), 3)); /// assert_eq!(ary.rassoc::<_, Option<(String, i64)>>(4)?, None); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn rassoc(self, value: K) -> Result where K: IntoValue, T: TryConvert, { let value = Ruby::get_with(self).into_value(value); protect(|| unsafe { Value::new(rb_ary_rassoc(self.as_rb_value(), value.as_rb_value())) }) .and_then(TryConvert::try_convert) } /// Recursively compares elements of the two arrays using Ruby's `<=>`. /// /// Returns `Some(Ordering::Equal)` if `self` and `other` are equal. /// Returns `Some(Ordering::Less)` if `self` if less than `other`. /// Returns `Some(Ordering::Greater)` if `self` if greater than `other`. /// Returns `None` if `self` and `other` are not comparable. /// /// # Examples /// /// ``` /// use std::cmp::Ordering; /// /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = ruby.ary_from_vec(vec![1, 2, 3]); /// let b = ruby.ary_from_vec(vec![1, 2, 3]); /// assert_eq!(a.cmp(b)?, Some(Ordering::Equal)); /// /// let c = ruby.ary_from_vec(vec![1, 2, 0]); /// assert_eq!(a.cmp(c)?, Some(Ordering::Greater)); /// /// let d = ruby.ary_from_vec(vec![1, 2, 4]); /// assert_eq!(a.cmp(d)?, Some(Ordering::Less)); /// /// let e = ruby.ary_from_vec(vec![1, 2]); /// e.push(())?; /// assert_eq!(a.cmp(e)?, None); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// Note that `std::cmp::Ordering` can be cast to `i{8,16,32,64,size}` to /// get the Ruby standard `-1`/`0`/`+1` for comparison results. /// /// ``` /// assert_eq!(std::cmp::Ordering::Less as i64, -1); /// assert_eq!(std::cmp::Ordering::Equal as i64, 0); /// assert_eq!(std::cmp::Ordering::Greater as i64, 1); /// ``` #[allow(clippy::should_implement_trait)] pub fn cmp(self, other: Self) -> Result, Error> { protect(|| unsafe { Value::new(rb_ary_cmp(self.as_rb_value(), other.as_rb_value())) }) .and_then(>::try_convert) .map(|opt| opt.map(|i| i.cmp(&0))) } } impl fmt::Display for RArray { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for RArray { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoIterator for RArray { type Item = Value; type IntoIter = Iter; /// Returns an [`Iter`] over a copy of `self`. /// /// `self` is copied using a fast copy-on-write optimisation, so if `self` /// is not modified then `self` and the copy will point to the same backing /// store and use no extra memory. /// /// The copy is skipped if `self` is frozen. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby, TryConvert}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_new(); /// ary.push(1)?; /// ary.push(2)?; /// ary.push(3)?; /// /// let iter = ary.into_iter(); /// /// ary.push(4)?; /// /// let res = iter /// .map(TryConvert::try_convert) /// .collect::, Error>>()?; /// /// assert_eq!(res, vec![1, 2, 3]); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn into_iter(self) -> Self::IntoIter { let ary = if self.is_frozen() { self } else { let tmp = self.dup(); unsafe { rb_obj_hide(tmp.as_rb_value()) }; tmp }; Iter::new(ary) } } impl IntoValue for RArray { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } macro_rules! impl_into_value { ($n:literal) => { seq!(N in 0..=$n { impl<#(T~N,)*> IntoValue for (#(T~N,)*) where #(T~N: IntoValue,)* { fn into_value_with(self, handle: &Ruby) -> Value { let ary = [ #(handle.into_value(self.N),)* ]; handle.ary_new_from_values(&ary).into_value_with(handle) } } unsafe impl<#(T~N,)*> IntoValueFromNative for (#(T~N,)*) where #(T~N: IntoValueFromNative,)* {} }); } } seq!(N in 0..12 { impl_into_value!(N); }); impl IntoValue for Vec where T: IntoValueFromNative, { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle.ary_from_vec(self).into_value_with(handle) } } unsafe impl IntoValueFromNative for Vec where T: IntoValueFromNative {} #[cfg(feature = "old-api")] impl FromIterator for RArray where T: IntoValue, { /// Creates a Ruby array from an iterator. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::ary_from_iter`] /// for the non-panicking version. fn from_iter(iter: I) -> Self where I: IntoIterator, { get_ruby!().ary_from_iter(iter) } } impl Object for RArray {} unsafe impl private::ReprValue for RArray {} impl ReprValue for RArray {} impl TryConvert for RArray { fn try_convert(val: Value) -> Result { if let Some(v) = Self::from_value(val) { return Ok(v); } unsafe { protect(|| Value::new(rb_check_array_type(val.as_rb_value()))).and_then(|res| { Self::from_value(res).ok_or_else(|| { Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into Array", val.class()), ) }) }) } } } /// A Ruby Array that may only contain elements of type `T`. /// /// On creation this Array is hidden from Ruby, and must be consumed to /// pass it to Ruby (where it reverts to a regular untyped Array). It is /// then inaccessible to Rust. /// /// See [`Ruby::typed_ary_new`] or [`RArray::typecheck`] for how to get a value /// of `TypedArray`. // // Very deliberately not Copy or Clone so that values of this type are consumed // when TypedArray::to_array is called, so you can either have typed access // from Rust *or* expose it to Ruby. #[repr(transparent)] pub struct TypedArray(NonZeroValue, PhantomData); macro_rules! proxy { ($method:ident($($arg:ident: $typ:ty),*) -> $ret:ty) => { #[doc=concat!("See [`RArray::", stringify!($method), "`].")] pub fn $method(&self, $($arg: $typ),*) -> $ret { unsafe { RArray::from_value_unchecked(self.0.get()) }.$method($($arg),*) } }; } impl TypedArray { /// Consume `self`, returning it as an [`RArray`]. pub fn to_r_array(self) -> RArray { let val = self.0.get(); let ruby = Ruby::get_with(val); unsafe { rb_obj_reveal(val.as_rb_value(), ruby.class_array().as_rb_value()); RArray::from_value_unchecked(val) } } proxy!(len() -> usize); proxy!(is_empty() -> bool); proxy!(clear() -> Result<(), Error>); proxy!(resize(len: usize) -> Result<(), Error>); proxy!(reverse() -> Result<(), Error>); proxy!(rotate(rot: isize) -> Result<(), Error>); proxy!(sort() -> Result<(), Error>); /// See [`RArray::dup`]. pub fn dup(&self) -> Self { unsafe { let dup = RArray::from_value_unchecked(self.0.get()).dup(); rb_obj_hide(dup.as_rb_value()); TypedArray(NonZeroValue::new_unchecked(dup.as_value()), PhantomData) } } /// See [`RArray::concat`]. pub fn concat(&self, other: Self) -> Result<(), Error> { unsafe { RArray::from_value_unchecked(self.0.get()) .concat(RArray::from_value_unchecked(other.0.get())) } } /// See [`RArray::plus`]. pub fn plus(&self, other: Self) -> Self { unsafe { let new_ary = RArray::from_value_unchecked(self.0.get()) .plus(RArray::from_value_unchecked(other.0.get())); rb_obj_hide(new_ary.as_rb_value()); TypedArray(NonZeroValue::new_unchecked(new_ary.as_value()), PhantomData) } } /// See [`RArray::as_slice`]. pub unsafe fn as_slice(&self) -> &[Value] { RArray::from_value_unchecked(self.0.get()).as_slice_unconstrained() } /// See [`RArray::to_value_array`]. pub fn to_value_array(&self) -> Result<[Value; N], Error> { unsafe { RArray::from_value_unchecked(self.0.get()).to_value_array() } } /// See [`RArray::join`]. pub fn join(&self, sep: S) -> Result where S: IntoRString, { unsafe { RArray::from_value_unchecked(self.0.get()).join(sep) } } // TODO is_shared /// See [`RArray::replace`]. pub fn replace(&self, from: Self) -> Result<(), Error> { unsafe { RArray::from_value_unchecked(self.0.get()) .replace(RArray::from_value_unchecked(from.0.get())) } } /// See [`RArray::subseq`]. pub fn subseq(&self, offset: usize, length: usize) -> Option { unsafe { RArray::from_value_unchecked(self.0.get()) .subseq(offset, length) .map(|ary| { rb_obj_hide(ary.as_rb_value()); TypedArray(NonZeroValue::new_unchecked(ary.as_value()), PhantomData) }) } } /// See [`RArray::subseq`]. #[allow(clippy::should_implement_trait)] pub fn cmp(&self, other: Self) -> Result, Error> { unsafe { RArray::from_value_unchecked(self.0.get()) .cmp(RArray::from_value_unchecked(other.0.get())) } } } impl TypedArray where T: IntoValue, { proxy!(includes(val: T) -> bool); proxy!(push(item: T) -> Result<(), Error>); proxy!(unshift(item: T) -> Result<(), Error>); proxy!(delete(item: T) -> Result<(), Error>); proxy!(store(offset: isize, val: T) -> Result<(), Error>); } impl TypedArray where T: ReprValue, { proxy!(cat(s: &[T]) -> Result<(), Error>); } impl TypedArray where T: TryConvert, { proxy!(pop() -> Result); proxy!(shift() -> Result); proxy!(delete_at(index: isize) -> Result); proxy!(entry(offset: isize) -> Result); /// See [`RArray::to_array`]. pub fn to_array(&self) -> Result<[T; N], Error> { unsafe { RArray::from_value_unchecked(self.0.get()).to_array() } } /// Returns an [`Iter`] over a copy of `self`. /// /// `self` is copied using a fast copy-on-write optimisation, so if `self` /// is not modified then `self` and the copy will point to the same backing /// store and use no extra memory. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.typed_ary_new(); /// ary.push(ruby.integer_from_i64(1))?; /// ary.push(ruby.integer_from_i64(2))?; /// ary.push(ruby.integer_from_i64(3))?; /// /// let iter = ary.iter(); /// /// ary.push(ruby.integer_from_i64(4))?; /// /// let res = iter /// .map(|int| int.to_usize()) /// .collect::, Error>>()?; /// /// assert_eq!(res, vec![1, 2, 3]); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn iter(&self) -> Iter { Iter::new(unsafe { RArray::from_value_unchecked(self.dup().0.get()) }) } // TODO? assoc & rassoc } impl TypedArray where T: TryConvertOwned, { /// See [`RArray::to_vec`]. pub fn to_vec(&self) -> Vec { unsafe { RArray::from_value_unchecked(self.0.get()).to_vec().unwrap() } } } impl IntoIterator for TypedArray where T: TryConvert, { type Item = T; type IntoIter = Iter; /// Returns an [`Iter`] over `self`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.typed_ary_new(); /// ary.push(ruby.integer_from_i64(1))?; /// ary.push(ruby.integer_from_i64(2))?; /// ary.push(ruby.integer_from_i64(3))?; /// /// let res = ary /// .into_iter() /// .map(|int| int.to_usize()) /// .collect::, Error>>()?; /// /// assert_eq!(res, vec![1, 2, 3]); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn into_iter(self) -> Self::IntoIter { Iter::new(unsafe { RArray::from_value_unchecked(self.0.get()) }) } } impl IntoValue for TypedArray where T: IntoValue, { fn into_value_with(self, _: &Ruby) -> Value { self.to_r_array().as_value() } } impl gc::private::Mark for TypedArray { fn raw(self) -> VALUE { self.0.get().as_rb_value() } } impl gc::Mark for TypedArray {} /// An iterator over the elements of an array. pub struct Iter { data: RArray, len: usize, idx: usize, item_type: PhantomData, } impl Iterator for Iter where T: TryConvert, { type Item = T; #[inline] fn next(&mut self) -> Option { if self.idx >= self.len { None } else { let value = self.data.entry(self.idx as isize).ok(); self.idx += 1; value } } fn size_hint(&self) -> (usize, Option) { let remaining = self.len - self.idx; (remaining, Some(remaining)) } } impl Iter { fn new(data: RArray) -> Self { Self { data, len: data.len(), idx: 0, item_type: PhantomData, } } } magnus-0.7.1/src/r_bignum.rs000064400000000000000000000335341046102023000140720ustar 00000000000000use std::{ fmt, os::raw::{c_long, c_longlong, c_ulong, c_ulonglong}, }; use rb_sys::{ rb_ll2inum, rb_num2ll, rb_num2long, rb_num2ull, rb_num2ulong, rb_ull2inum, ruby_fl_type, ruby_value_type, VALUE, }; use crate::{ error::{protect, Error}, integer::{Integer, IntegerType}, into_value::IntoValue, numeric::Numeric, try_convert::TryConvert, value::{ private::{self, ReprValue as _}, Fixnum, NonZeroValue, ReprValue, Value, }, Ruby, }; /// # `RBignum` /// /// Functions that can be used to create instances of Ruby's large interger /// representation. /// /// See also the [`RBignum`] type. impl Ruby { /// Create a new `RBignum` from an `i64.` /// /// Returns `Ok(RBignum)` if `n` is large enough to require a bignum, /// otherwise returns `Err(Fixnum)`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ruby.bignum_from_i64(4611686018427387904).is_ok()); /// assert!(ruby.bignum_from_i64(-4611686018427387905).is_ok()); /// // too small /// assert!(ruby.bignum_from_i64(0).is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn bignum_from_i64(&self, n: i64) -> Result { unsafe { let val = Value::new(rb_ll2inum(n)); RBignum::from_value(val) .ok_or_else(|| Fixnum::from_rb_value_unchecked(val.as_rb_value())) } } /// Create a new `RBignum` from an `u64.` /// /// Returns `Ok(RBignum)` if `n` is large enough to require a bignum, /// otherwise returns `Err(Fixnum)`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ruby.bignum_from_u64(4611686018427387904).is_ok()); /// // too small /// assert!(ruby.bignum_from_u64(0).is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn bignum_from_u64(&self, n: u64) -> Result { unsafe { let val = Value::new(rb_ull2inum(n)); RBignum::from_value(val) .ok_or_else(|| Fixnum::from_rb_value_unchecked(val.as_rb_value())) } } } /// A Value pointer to a RBignum struct, Ruby's internal representation of /// large integers. /// /// See also [`Integer`]. /// /// See the [`ReprValue`] trait for additional methods available on this type. /// See [`Ruby`](Ruby#rbignum) for methods to create an `RBignum`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct RBignum(NonZeroValue); impl RBignum { /// Return `Some(RBignum)` if `val` is a `RBignum`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, RBignum}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(RBignum::from_value(eval("9223372036854775807").unwrap()).is_some()); /// // too small /// assert!(RBignum::from_value(eval("0").unwrap()).is_none()); /// // not an int /// assert!(RBignum::from_value(eval("1.23").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { (val.rb_type() == ruby_value_type::RUBY_T_BIGNUM) .then(|| Self(NonZeroValue::new_unchecked(val))) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } /// Create a new `RBignum` from an `i64.` /// /// Returns `Ok(RBignum)` if `n` is large enough to require a bignum, /// otherwise returns `Err(Fixnum)`. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::bignum_from_i64`] /// for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::RBignum; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(RBignum::from_i64(4611686018427387904).is_ok()); /// assert!(RBignum::from_i64(-4611686018427387905).is_ok()); /// // too small /// assert!(RBignum::from_i64(0).is_err()); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::bignum_from_i64` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn from_i64(n: i64) -> Result { get_ruby!().bignum_from_i64(n) } /// Create a new `RBignum` from an `u64.` /// /// Returns `Ok(RBignum)` if `n` is large enough to require a bignum, /// otherwise returns `Err(Fixnum)`. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::bignum_from_u64`] /// for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::RBignum; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(RBignum::from_u64(4611686018427387904).is_ok()); /// // too small /// assert!(RBignum::from_u64(0).is_err()); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::bignum_from_u64` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn from_u64(n: u64) -> Result { get_ruby!().bignum_from_u64(n) } /// Create a new `RBignum` from a `i32.` /// /// This will only succeed on a 32 bit system. On a 64 bit system bignum /// will always be out of range. #[doc(hidden)] pub fn to_i32(self) -> Result { debug_assert_value!(self); let handle = Ruby::get_with(self); let mut res = 0; protect(|| { unsafe { res = rb_num2long(self.as_rb_value()) }; handle.qnil() })?; if res > i32::MAX as c_long { return Err(Error::new( handle.exception_range_error(), "bignum too big to convert into `i32`", )); } Ok(res as i32) } /// Convert `self` to an `i64`. Returns `Err` if `self` is out of range for /// `i64`. /// /// # Examples /// /// ``` /// use magnus::{eval, RBignum}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!( /// eval::("4611686018427387904") /// .unwrap() /// .to_i64() /// .unwrap(), /// 4611686018427387904 /// ); /// assert_eq!( /// eval::("-4611686018427387905") /// .unwrap() /// .to_i64() /// .unwrap(), /// -4611686018427387905 /// ); /// assert!(eval::("9223372036854775808") /// .unwrap() /// .to_i64() /// .is_err()); /// assert!(eval::("-9223372036854775809") /// .unwrap() /// .to_i64() /// .is_err()); /// ``` pub fn to_i64(self) -> Result { debug_assert_value!(self); let mut res = 0; protect(|| { unsafe { res = rb_num2ll(self.as_rb_value()) }; Ruby::get_with(self).qnil() })?; Ok(res) } /// Convert `self` to an `isize`. Returns `Err` if `self` is out of range /// for `isize`. /// /// # Examples /// /// ``` /// use magnus::{eval, RBignum}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!( /// eval::("4611686018427387904") /// .unwrap() /// .to_isize() /// .unwrap(), /// 4611686018427387904 /// ); /// assert_eq!( /// eval::("-4611686018427387905") /// .unwrap() /// .to_isize() /// .unwrap(), /// -4611686018427387905 /// ); /// ``` pub fn to_isize(self) -> Result { debug_assert_value!(self); let handle = Ruby::get_with(self); let mut res = 0; protect(|| { unsafe { res = rb_num2ll(self.as_rb_value()) }; handle.qnil() })?; if res > isize::MAX as c_longlong { return Err(Error::new( handle.exception_range_error(), "bignum too big to convert into `isize`", )); } Ok(res as isize) } /// Create a new `RBignum` from a `u32.` /// /// This will only succeed on a 32 bit system. On a 64 bit system bignum /// will always be out of range. #[doc(hidden)] pub fn to_u32(self) -> Result { debug_assert_value!(self); let handle = Ruby::get_with(self); if self.is_negative() { return Err(Error::new( handle.exception_range_error(), "can't convert negative integer to unsigned", )); } let mut res = 0; protect(|| { unsafe { res = rb_num2ulong(self.as_rb_value()) }; handle.qnil() })?; if res > u32::MAX as c_ulong { return Err(Error::new( handle.exception_range_error(), "bignum too big to convert into `u32`", )); } Ok(res as u32) } /// Convert `self` to a `u64`. Returns `Err` if `self` is negative or out /// of range for `u64`. /// /// # Examples /// /// ``` /// use magnus::{eval, RBignum}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!( /// eval::("4611686018427387904") /// .unwrap() /// .to_u64() /// .unwrap(), /// 4611686018427387904 /// ); /// assert!(eval::("18446744073709551616") /// .unwrap() /// .to_u64() /// .is_err()); /// ``` pub fn to_u64(self) -> Result { debug_assert_value!(self); let handle = Ruby::get_with(self); if self.is_negative() { return Err(Error::new( handle.exception_range_error(), "can't convert negative integer to unsigned", )); } let mut res = 0; protect(|| { unsafe { res = rb_num2ull(self.as_rb_value()) }; handle.qnil() })?; Ok(res) } /// Convert `self` to a `usize`. Returns `Err` if `self` is negative or out /// of range for `usize`. /// /// # Examples /// /// ``` /// use magnus::{eval, RBignum}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert_eq!( /// eval::("4611686018427387904") /// .unwrap() /// .to_usize() /// .unwrap(), /// 4611686018427387904 /// ); /// assert!(eval::("18446744073709551616") /// .unwrap() /// .to_usize() /// .is_err()); /// ``` pub fn to_usize(self) -> Result { debug_assert_value!(self); let handle = Ruby::get_with(self); if self.is_negative() { return Err(Error::new( handle.exception_range_error(), "can't convert negative integer to unsigned", )); } let mut res = 0; protect(|| { unsafe { res = rb_num2ull(self.as_rb_value()) }; handle.qnil() })?; if res > usize::MAX as c_ulonglong { return Err(Error::new( handle.exception_range_error(), "bignum too big to convert into `usize`", )); } Ok(res as usize) } /// Check if `self` is positive. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let num = ruby.bignum_from_u64(4611686018427387904).unwrap(); /// assert!(num.is_positive()); /// /// let num = ruby.bignum_from_i64(-4611686018427387905).unwrap(); /// assert!(!num.is_positive()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn is_positive(self) -> bool { debug_assert_value!(self); unsafe { let r_basic = self.r_basic_unchecked(); r_basic.as_ref().flags & (ruby_fl_type::RUBY_FL_USER1 as VALUE) != 0 } } /// Check if `self` is negative. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let num = ruby.bignum_from_i64(-4611686018427387905).unwrap(); /// assert!(num.is_negative()); /// /// let num = ruby.bignum_from_u64(4611686018427387904).unwrap(); /// assert!(!num.is_negative()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn is_negative(self) -> bool { !self.is_positive() } } impl fmt::Display for RBignum { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for RBignum { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for RBignum { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } unsafe impl private::ReprValue for RBignum {} impl Numeric for RBignum {} impl ReprValue for RBignum {} impl TryConvert for RBignum { fn try_convert(val: Value) -> Result { match Integer::try_convert(val)?.integer_type() { IntegerType::Fixnum(_) => Err(Error::new( Ruby::get_with(val).exception_range_error(), "integer to small for bignum", )), IntegerType::Bignum(big) => Ok(big), } } } magnus-0.7.1/src/r_complex.rs000064400000000000000000000155711046102023000142610ustar 00000000000000use std::fmt; use rb_sys::{ rb_complex_abs, rb_complex_arg, rb_complex_conjugate, rb_complex_imag, rb_complex_new, rb_complex_new_polar, rb_complex_real, ruby_value_type, VALUE, }; use crate::{ error::{protect, Error}, float::Float, into_value::IntoValue, numeric::Numeric, try_convert::TryConvert, value::{ private::{self, ReprValue as _}, NonZeroValue, ReprValue, Value, }, Ruby, }; /// A Value pointer to a RComplex struct, Ruby's internal representation of /// complex numbers. /// /// See the [`ReprValue`] trait for additional methods available on this type. #[derive(Clone, Copy)] #[repr(transparent)] pub struct RComplex(NonZeroValue); impl RComplex { /// Return `Some(RComplex)` if `val` is a `RComplex`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, RComplex}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(RComplex::from_value(eval("2+1i").unwrap()).is_some()); /// assert!(RComplex::from_value(eval("3").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { (val.rb_type() == ruby_value_type::RUBY_T_COMPLEX) .then(|| Self(NonZeroValue::new_unchecked(val))) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } /// Create a new `RComplex`. /// /// # Examples /// /// ``` /// use magnus::{Error, RComplex, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let complex = RComplex::new(ruby.integer_from_i64(2), ruby.integer_from_i64(1)); /// assert_eq!(complex.to_string(), "2+1i"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn new(real: T, imag: U) -> RComplex where T: Numeric, U: Numeric, { unsafe { RComplex::from_rb_value_unchecked(rb_complex_new( real.as_rb_value(), imag.as_rb_value(), )) } } /// Create a new `RComplex` using polar representation. /// /// # Examples /// /// ``` /// use magnus::{Error, RComplex, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let complex = RComplex::polar(ruby.integer_from_i64(2), ruby.integer_from_i64(3))?; /// assert_eq!( /// complex.to_string(), /// "-1.9799849932008908+0.2822400161197344i" /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn polar(real: T, imag: U) -> Result where T: Numeric, U: Numeric, { protect(|| unsafe { RComplex::from_rb_value_unchecked(rb_complex_new_polar( real.as_rb_value(), imag.as_rb_value(), )) }) } /// Returns the real part of `self`. /// /// # Examples /// /// ``` /// use magnus::{Error, RComplex, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let complex = RComplex::new(ruby.integer_from_i64(9), ruby.integer_from_i64(-4)); /// assert_eq!(complex.real::()?, 9); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn real(self) -> Result where T: TryConvert, { let val = unsafe { Value::new(rb_complex_real(self.as_rb_value())) }; T::try_convert(val) } /// Returns the imaginary part of `self`. /// /// # Examples /// /// ``` /// use magnus::{Error, RComplex, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let complex = RComplex::new(ruby.integer_from_i64(9), ruby.integer_from_i64(-4)); /// assert_eq!(complex.imag::()?, -4); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn imag(self) -> Result where T: TryConvert, { let val = unsafe { Value::new(rb_complex_imag(self.as_rb_value())) }; T::try_convert(val) } /// Returns the complex conjugate. /// /// # Examples /// /// ``` /// use magnus::{Error, RComplex, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let complex = RComplex::new(ruby.integer_from_i64(1), ruby.integer_from_i64(2)); /// assert_eq!(complex.conjugate().to_string(), "1-2i"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn conjugate(self) -> Self { unsafe { Self::from_rb_value_unchecked(rb_complex_conjugate(self.as_rb_value())) } } /// Returns the absolute (or the magnitude) of `self`. /// /// # Examples /// /// ``` /// use magnus::{Error, RComplex, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let complex = RComplex::new(ruby.integer_from_i64(3), ruby.integer_from_i64(-4)); /// assert_eq!(complex.abs(), 5.0); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn abs(self) -> f64 { unsafe { Float::from_rb_value_unchecked(rb_complex_abs(self.as_rb_value())).to_f64() } } /// Returns the argument (or the angle) of the polar form of `self`. /// /// # Examples /// /// ``` /// use std::f64::consts::PI; /// /// use magnus::{Error, RComplex, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let complex = RComplex::polar(ruby.integer_from_i64(3), ruby.float_from_f64(PI / 2.0))?; /// assert_eq!(complex.arg(), 1.5707963267948966); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn arg(self) -> f64 { unsafe { Float::from_rb_value_unchecked(rb_complex_arg(self.as_rb_value())).to_f64() } } } impl fmt::Display for RComplex { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for RComplex { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for RComplex { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } unsafe impl private::ReprValue for RComplex {} impl Numeric for RComplex {} impl ReprValue for RComplex {} impl TryConvert for RComplex { fn try_convert(val: Value) -> Result { Self::from_value(val).ok_or_else(|| { Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into Complex", unsafe { val.classname() },), ) }) } } magnus-0.7.1/src/r_file.rs000064400000000000000000000060471046102023000135270ustar 00000000000000use std::fmt; #[cfg(ruby_lt_3_3)] use std::ptr::NonNull; #[cfg(ruby_gte_3_3)] use rb_sys::rb_io_descriptor; use rb_sys::ruby_value_type; use crate::{ error::Error, into_value::IntoValue, object::Object, try_convert::TryConvert, value::{ private::{self, ReprValue as _}, NonZeroValue, ReprValue, Value, }, Ruby, }; /// A Value pointer to a RFile struct, Ruby's internal representation of IO. /// /// See the [`ReprValue`] and [`Object`] traits for additional methods /// available on this type. #[derive(Clone, Copy)] #[repr(transparent)] pub struct RFile(NonZeroValue); impl RFile { /// Return `Some(RFile)` if `val` is a `RFile`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, RFile}; /// # let ruby = unsafe { magnus::embed::init() }; /// /// assert!(RFile::from_value(eval("STDOUT").unwrap()).is_some()); /// # #[cfg(not(windows))] /// # { /// assert!(RFile::from_value(eval(r#"File.open("/tmp/example.txt", "w+")"#).unwrap()).is_some()); /// # ruby.require("socket").unwrap(); /// assert!(RFile::from_value(eval("UNIXSocket.pair.first").unwrap()).is_some()); /// # } /// assert!(RFile::from_value(eval("nil").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { (val.rb_type() == ruby_value_type::RUBY_T_FILE) .then(|| Self(NonZeroValue::new_unchecked(val))) } } #[cfg(ruby_lt_3_3)] fn as_internal(self) -> NonNull { // safe as inner value is NonZero unsafe { NonNull::new_unchecked(self.0.get().as_rb_value() as *mut _) } } } impl fmt::Display for RFile { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for RFile { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for RFile { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl Object for RFile {} unsafe impl private::ReprValue for RFile {} impl ReprValue for RFile {} impl TryConvert for RFile { fn try_convert(val: Value) -> Result { Self::from_value(val).ok_or_else(|| { Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into File", unsafe { val.classname() },), ) }) } } #[cfg(not(unix))] pub mod fd { use std::os::raw::c_int; pub type RawFd = c_int; pub trait AsRawFd { fn as_raw_fd(&self) -> RawFd; } } #[cfg(unix)] pub use std::os::unix::io as fd; impl fd::AsRawFd for RFile { #[cfg(ruby_gte_3_3)] fn as_raw_fd(&self) -> fd::RawFd { unsafe { rb_io_descriptor(self.as_rb_value()) } } #[cfg(ruby_lt_3_3)] fn as_raw_fd(&self) -> fd::RawFd { unsafe { (*self.as_internal().as_ref().fptr).fd } } } magnus-0.7.1/src/r_float.rs000064400000000000000000000157451046102023000137220ustar 00000000000000use std::fmt; use rb_sys::{rb_float_new, rb_float_value, ruby_value_type, VALUE}; #[cfg(ruby_use_flonum)] use crate::value::Flonum; use crate::{ error::Error, float::Float, into_value::IntoValue, numeric::Numeric, try_convert::TryConvert, value::{ private::{self, ReprValue as _}, NonZeroValue, ReprValue, Value, }, Ruby, }; /// # `RFloat` /// /// Functions that can be used to create Ruby `Float`s. /// /// See also the [`RFloat`] type. impl Ruby { /// Create a new `RFloat` from an `f64.` /// /// Returns `Ok(RFloat)` if `n` requires a high precision float, otherwise /// returns `Err(Flonum)`. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let f = ruby.r_float_from_f64(1.7272337110188890e-77).unwrap(); /// rb_assert!(ruby, "f == 1.7272337110188890e-77", f); /// /// // can fit within a Flonum, so does not require an RFloat /// assert!(ruby.r_float_from_f64(1.7272337110188893e-77).is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[cfg(ruby_use_flonum)] pub fn r_float_from_f64(&self, n: f64) -> Result { unsafe { let val = Value::new(rb_float_new(n)); RFloat::from_value(val) .ok_or_else(|| Flonum::from_rb_value_unchecked(val.as_rb_value())) } } /// Create a new `RFloat` from an `f64.` /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let f = ruby.r_float_from_f64(1.7272337110188893e-77).unwrap(); /// rb_assert!(ruby, "f == 1.7272337110188893e-77", f); /// /// let f = ruby.r_float_from_f64(1.7272337110188890e-77).unwrap(); /// rb_assert!(ruby, "f == 1.7272337110188890e-77", f); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[cfg(not(ruby_use_flonum))] pub fn r_float_from_f64(&self, n: f64) -> Result { unsafe { Ok(RFloat::from_rb_value_unchecked(rb_float_new(n))) } } } /// A Value pointer to an RFloat struct, Ruby's internal representation of /// high precision floating point numbers. /// /// See also [`Float`] and [`Flonum`]. /// /// See the [`ReprValue`] trait for additional methods available on this type. /// See [`Ruby`](Ruby#rfloat) for methods to create an `RFloat`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct RFloat(NonZeroValue); impl RFloat { /// Return `Some(RFloat)` if `val` is a `RFloat`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, RFloat}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(RFloat::from_value(eval("1.7272337110188890e-77").unwrap()).is_some()); /// // can fit within a Flonum, so does not require an RFloat /// assert!(RFloat::from_value(eval("1.7272337110188893e-77").unwrap()).is_none()); /// // not an RFloat /// assert!(RFloat::from_value(eval("1").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { (val.rb_type() == ruby_value_type::RUBY_T_FLOAT && (!cfg!(ruby_use_flonum) || !val.is_flonum())) .then(|| Self(NonZeroValue::new_unchecked(val))) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } /// Create a new `RFloat` from an `f64.` /// /// Returns `Ok(RFloat)` if `n` requires a high precision float, otherwise /// returns `Err(Flonum)`. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::r_float_from_f64`] /// for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{rb_assert, RFloat}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let f = RFloat::from_f64(1.7272337110188890e-77).unwrap(); /// rb_assert!("f == 1.7272337110188890e-77", f); /// /// // can fit within a Flonum, so does not require an RFloat /// assert!(RFloat::from_f64(1.7272337110188893e-77).is_err()); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::r_float_from_f64` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[cfg(ruby_use_flonum)] #[inline] pub fn from_f64(n: f64) -> Result { get_ruby!().r_float_from_f64(n) } /// Create a new `RFloat` from an `f64.` /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::r_float_from_f64`] /// for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{rb_assert, RFloat}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let f = RFloat::from_f64(1.7272337110188893e-77).unwrap(); /// rb_assert!("f == 1.7272337110188893e-77", f); /// /// let f = RFloat::from_f64(1.7272337110188890e-77).unwrap(); /// rb_assert!("f == 1.7272337110188890e-77", f); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::r_float_from_f64` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[cfg(not(ruby_use_flonum))] #[inline] pub fn from_f64(n: f64) -> Result { get_ruby!().r_float_from_f64(n) } /// Convert `self` to a `f64`. /// /// # Examples /// /// ``` /// use magnus::{eval, RFloat}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let f: RFloat = eval("1.7272337110188890e-77").unwrap(); /// assert_eq!(f.to_f64(), 1.7272337110188890e-77); /// ``` pub fn to_f64(self) -> f64 { debug_assert_value!(self); unsafe { rb_float_value(self.as_rb_value()) } } } impl fmt::Display for RFloat { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for RFloat { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for RFloat { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl Numeric for RFloat {} unsafe impl private::ReprValue for RFloat {} impl ReprValue for RFloat {} impl TryConvert for RFloat { fn try_convert(val: Value) -> Result { let float = Float::try_convert(val)?; if let Some(rfloat) = RFloat::from_value(float.as_value()) { Ok(rfloat) } else { Err(Error::new( Ruby::get_with(val).exception_range_error(), "float in range for flonum", )) } } } magnus-0.7.1/src/r_hash.rs000064400000000000000000000712671046102023000135410ustar 00000000000000//! Types and functions for working with Ruby’s Hash class. use std::{ collections::HashMap, convert::Infallible, fmt, hash::Hash, os::raw::{c_int, c_long}, panic::AssertUnwindSafe, }; #[cfg(ruby_gte_3_2)] use rb_sys::rb_hash_new_capa; use rb_sys::{ rb_check_hash_type, rb_hash_aref, rb_hash_aset, rb_hash_bulk_insert, rb_hash_clear, rb_hash_delete, rb_hash_fetch, rb_hash_foreach, rb_hash_lookup, rb_hash_lookup2, rb_hash_new, rb_hash_size, rb_hash_size_num, rb_hash_update_by, ruby_value_type, VALUE, }; use crate::{ error::{protect, raise, Error}, into_value::{IntoValue, IntoValueFromNative}, object::Object, try_convert::{TryConvert, TryConvertOwned}, value::{ private::{self, ReprValue as _}, Fixnum, NonZeroValue, ReprValue, Value, QUNDEF, }, Ruby, }; /// Iteration state for [`RHash::foreach`]. #[repr(u32)] pub enum ForEach { /// Continue iterating. Continue, /// Stop iterating. Stop, /// Delete the last entry and continue iterating. Delete, } // Helper trait for wrapping a function with type conversions and error // handling for `RHash::foreach`. trait ForEachCallback where Self: Sized + FnMut(K, V) -> Result, K: TryConvert, V: TryConvert, { #[inline] unsafe fn call_convert_value(mut self, key: Value, value: Value) -> Result { (self)( TryConvert::try_convert(key)?, TryConvert::try_convert(value)?, ) } #[inline] unsafe fn call_handle_error(self, key: Value, value: Value) -> ForEach { let res = match std::panic::catch_unwind(AssertUnwindSafe(|| { self.call_convert_value(key, value) })) { Ok(v) => v, Err(e) => Err(Error::from_panic(e)), }; match res { Ok(v) => v, Err(e) => raise(e), } } } impl ForEachCallback for Func where Func: FnMut(K, V) -> Result, K: TryConvert, V: TryConvert, { } /// # `RHash` /// /// Functions that can be used to create Ruby `Hash`es. /// /// See also the [`RHash`] type. impl Ruby { /// Create a new empty `RHash`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let hash = ruby.hash_new(); /// assert!(hash.is_empty()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn hash_new(&self) -> RHash { unsafe { RHash::from_rb_value_unchecked(rb_hash_new()) } } /// Create a new empty `RHash` with capacity for `n` elements pre-allocated. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.hash_new_capa(16); /// assert!(ary.is_empty()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[cfg(any(ruby_gte_3_2, docsrs))] #[cfg_attr(docsrs, doc(cfg(ruby_gte_3_2)))] pub fn hash_new_capa(&self, n: usize) -> RHash { unsafe { RHash::from_rb_value_unchecked(rb_hash_new_capa(n as c_long)) } } /// Create a new `RHash` from a Rust iterator. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let hash = ruby.hash_from_iter(["a", "b", "c"].into_iter().zip(1..4)); /// rb_assert!(ruby, r#"hash == {"a" => 1, "b" => 2, "c" => 3}"#, hash); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn hash_from_iter(&self, iter: I) -> RHash where I: IntoIterator, K: IntoValue, V: IntoValue, { self.hash_try_from_iter(iter.into_iter().map(Result::<_, Infallible>::Ok)) .unwrap() } /// Create a new `RHash` from a fallible Rust iterator. /// /// Returns `Ok(RHash)` on sucess or `Err(E)` with the first error /// encountered. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let hash = ruby.hash_try_from_iter("a,1;b,2;c,3".split(';').map(|s| { /// s.split_once(',') /// .ok_or_else(|| Error::new(ruby.exception_runtime_error(), "bad format")) /// }))?; /// rb_assert!( /// ruby, /// r#"hash == {"a" => "1", "b" => "2", "c" => "3"}"#, /// hash /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let err = ruby /// .hash_try_from_iter("a,1;b 2;c,3".split(';').map(|s| { /// s.split_once(',') /// .ok_or_else(|| Error::new(ruby.exception_runtime_error(), "bad format")) /// })) /// .unwrap_err(); /// assert_eq!(err.to_string(), "RuntimeError: bad format"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn hash_try_from_iter(&self, iter: I) -> Result where I: IntoIterator>, K: IntoValue, V: IntoValue, { #[cfg(ruby_gte_3_2)] pub fn hash_maybe_capa(n: usize) -> RHash { unsafe { Ruby::get_unchecked() }.hash_new_capa(n) } #[cfg(ruby_lt_3_2)] pub fn hash_maybe_capa(_: usize) -> RHash { unsafe { Ruby::get_unchecked() }.hash_new() } let iter = iter.into_iter(); let (lower, _) = iter.size_hint(); let hash = if lower > 0 { hash_maybe_capa(lower) } else { self.hash_new() }; let mut buffer = [self.qnil().as_value(); 128]; let mut i = 0; for r in iter { let (k, v) = r?; buffer[i] = self.into_value(k); buffer[i + 1] = self.into_value(v); i += 2; if i >= buffer.len() { i = 0; hash.bulk_insert(&buffer).unwrap(); } } hash.bulk_insert(&buffer[..i]).unwrap(); Ok(hash) } } /// A Value pointer to a RHash struct, Ruby's internal representation of Hash /// objects. /// /// See the [`ReprValue`] and [`Object`] traits for additional methods /// available on this type. See [`Ruby`](Ruby#rhash) for methods to create an /// `RHash`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct RHash(NonZeroValue); impl RHash { /// Return `Some(RHash)` if `val` is a `RHash`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, RHash}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(RHash::from_value(eval(r#"{"answer" => 42}"#).unwrap()).is_some()); /// assert!(RHash::from_value(eval("[]").unwrap()).is_none()); /// assert!(RHash::from_value(eval("nil").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { (val.rb_type() == ruby_value_type::RUBY_T_HASH) .then(|| Self(NonZeroValue::new_unchecked(val))) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } /// Create a new empty `RHash`. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::hash_new`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::RHash; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let hash = RHash::new(); /// assert!(hash.is_empty()); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::hash_new` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn new() -> RHash { get_ruby!().hash_new() } /// Create a new empty `RHash` with capacity for `n` elements /// pre-allocated. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::hash_new_capa`] /// for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::RHash; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let ary = RHash::with_capacity(16); /// assert!(ary.is_empty()); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::hash_new_capa` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[cfg(any(ruby_gte_3_2, docsrs))] #[cfg_attr(docsrs, doc(cfg(ruby_gte_3_2)))] #[inline] pub fn with_capacity(n: usize) -> Self { get_ruby!().hash_new_capa(n) } /// Set the value `val` for the key `key`. /// /// Errors if `self` is frozen or `key` does not respond to `hash`. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let hash = ruby.hash_new(); /// hash.aset("answer", 42)?; /// rb_assert!(ruby, r#"hash == {"answer" => 42}"#, hash); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn aset(self, key: K, val: V) -> Result<(), Error> where K: IntoValue, V: IntoValue, { let handle = Ruby::get_with(self); let key = handle.into_value(key); let val = handle.into_value(val); unsafe { protect(|| { Value::new(rb_hash_aset( self.as_rb_value(), key.as_rb_value(), val.as_rb_value(), )) })?; } Ok(()) } /// Insert a list of key-value pairs into a hash at once. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let hash = ruby.hash_new(); /// hash.bulk_insert(&[ /// ruby.to_symbol("given_name").as_value(), /// ruby.str_new("Arthur").as_value(), /// ruby.to_symbol("family_name").as_value(), /// ruby.str_new("Dent").as_value(), /// ]) /// .unwrap(); /// rb_assert!( /// ruby, /// r#"hash == {given_name: "Arthur", family_name: "Dent"}"#, /// hash, /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn bulk_insert(self, slice: &[T]) -> Result<(), Error> where T: ReprValue, { let ptr = slice.as_ptr() as *const VALUE; protect(|| { unsafe { rb_hash_bulk_insert(slice.len() as c_long, ptr, self.as_rb_value()) }; Ruby::get_with(self).qnil() })?; Ok(()) } /// Merges two hashes into one. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, RHash, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a: RHash = ruby.eval("{a: 1, b: 2}")?; /// let b: RHash = ruby.eval("{b: 3, c: 4}")?; /// a.update(b)?; /// /// // a is mutated, in case of conflicts b wins /// rb_assert!(ruby, "a == {a: 1, b: 3, c: 4}", a); /// /// // b is unmodified /// rb_assert!(ruby, "b == {b: 3, c: 4}", b); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` // // Implementation note: `rb_hash_update_by` takes a third optional argument, // a function pointer, the function being called to resolve conflicts. // Unfortunately there's no way to wrap this in a easy to use and safe Rust // api, so it has been omitted. pub fn update(self, other: RHash) -> Result<(), Error> { protect(|| { unsafe { rb_hash_update_by(self.as_rb_value(), other.as_rb_value(), None) }; Ruby::get_with(self).qnil() })?; Ok(()) } /// Return the value for `key`, converting it to `U`. /// /// Returns hash's default if `key` is missing. See also /// [`lookup`](RHash::lookup), [`lookup2`](RHash::lookup2), /// [`get`](RHash::get), and [`fetch`](RHash::fetch). /// /// # Examples /// /// ``` /// use magnus::{value::Qnil, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let hash = ruby.hash_new(); /// hash.aset("answer", 42)?; /// assert_eq!(hash.aref::<_, i64>("answer")?, 42); /// assert!(hash.aref::<_, Qnil>("missing").is_ok()); /// assert_eq!(hash.aref::<_, Option>("answer")?, Some(42)); /// assert_eq!(hash.aref::<_, Option>("missing")?, None); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{Error, RHash, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let hash: RHash = ruby.eval( /// r#" /// hash = {"answer" => 42} /// hash.default = 0 /// hash /// "#, /// )?; /// assert_eq!(hash.aref::<_, i64>("answer")?, 42); /// assert_eq!(hash.aref::<_, i64>("missing")?, 0); /// assert_eq!(hash.aref::<_, i64>(())?, 0); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn aref(self, key: T) -> Result where T: IntoValue, U: TryConvert, { let key = Ruby::get_with(self).into_value(key); protect(|| unsafe { Value::new(rb_hash_aref(self.as_rb_value(), key.as_rb_value())) }) .and_then(TryConvert::try_convert) } /// Return the value for `key`, converting it to `U`. /// /// Returns `nil` if `key` is missing. See also [`aref`](RHash::aref), /// [`lookup2`](RHash::lookup2), [`get`](RHash::get), and /// [`fetch`](RHash::fetch). /// /// # Examples /// /// ``` /// use magnus::{value::Qnil, Error, RHash, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let hash: RHash = ruby.eval( /// r#" /// hash = {"answer" => 42} /// hash.default = 0 /// hash /// "#, /// )?; /// assert_eq!(hash.lookup::<_, i64>("answer")?, 42); /// assert!(hash.lookup::<_, Qnil>("missing").is_ok()); /// assert_eq!(hash.lookup::<_, Option>("answer")?, Some(42)); /// assert_eq!(hash.lookup::<_, Option>("missing")?, None); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn lookup(self, key: T) -> Result where T: IntoValue, U: TryConvert, { let key = Ruby::get_with(self).into_value(key); protect(|| unsafe { Value::new(rb_hash_lookup(self.as_rb_value(), key.as_rb_value())) }) .and_then(TryConvert::try_convert) } /// Return the value for `key` or the provided `default`, converting to `U`. /// /// Returns `default` if `key` is missing. See also [`aref`](RHash::aref), /// [`lookup`](RHash::lookup), [`get`](RHash::get), and /// [`fetch`](RHash::fetch). /// /// # Examples /// /// ``` /// use magnus::{Error, RHash, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let hash: RHash = ruby.eval( /// r#" /// hash = {"foo" => 1, "bar" => nil} /// hash.default = 0 /// hash /// "#, /// )?; /// assert_eq!(hash.lookup2::<_, _, i64>("foo", -1)?, 1); /// assert_eq!(hash.lookup2::<_, _, Option>("foo", -1)?, Some(1)); /// assert_eq!(hash.lookup2::<_, _, Option>("bar", -1)?, None); /// assert_eq!(hash.lookup2::<_, _, Option>("baz", -1)?, Some(-1)); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn lookup2(self, key: T, default: U) -> Result where T: IntoValue, U: IntoValue, V: TryConvert, { let ruby = Ruby::get_with(self); let key = ruby.into_value(key); let default = ruby.into_value(default); protect(|| unsafe { Value::new(rb_hash_lookup2( self.as_rb_value(), key.as_rb_value(), default.as_rb_value(), )) }) .and_then(TryConvert::try_convert) } /// Return the value for `key` as a [`Value`]. /// /// Returns `None` if `key` is missing. See also [`aref`](RHash::aref), /// [`lookup`](RHash::lookup), [`lookup2`](RHash::lookup2), and /// [`fetch`](RHash::fetch). /// /// Note: It is possible for very badly behaved key objects to raise an /// error during hash lookup. This is unlikely, and for the simplicity of /// this api any errors will result in `None`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let hash = ruby.hash_new(); /// hash.aset("answer", 42)?; /// assert!(hash.get("answer").is_some()); /// assert!(hash.get("missing").is_none()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn get(self, key: T) -> Option where T: IntoValue, { let key = Ruby::get_with(self).into_value(key); protect(|| unsafe { Value::new(rb_hash_lookup2( self.as_rb_value(), key.as_rb_value(), QUNDEF.as_value().as_rb_value(), )) }) .ok() .and_then(|v| (!v.is_undef()).then(|| v)) } /// Return the value for `key`, converting it to `U`. /// /// Returns `Err` if `key` is missing. See also [`aref`](RHash::aref), /// [`lookup`](RHash::lookup), [`lookup2`](RHash::lookup2), and /// [`get`](RHash::get). /// /// # Examples /// /// ``` /// use magnus::{Error, RHash, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let hash: RHash = ruby.eval( /// r#" /// hash = {"answer" => 42} /// hash.default = 0 /// hash /// "#, /// )?; /// assert_eq!(hash.fetch::<_, i64>("answer")?, 42); /// assert!(hash.fetch::<_, i64>("missing").is_err()); /// assert_eq!(hash.fetch::<_, Option>("answer")?, Some(42)); /// assert!(hash.fetch::<_, Option>("missing").is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn fetch(self, key: T) -> Result where T: IntoValue, U: TryConvert, { let key = Ruby::get_with(self).into_value(key); protect(|| unsafe { Value::new(rb_hash_fetch(self.as_rb_value(), key.as_rb_value())) }) .and_then(TryConvert::try_convert) } /// Removes the key `key` from self and returns the associated value, /// converting it to `U`. /// /// Returns `nil` if `key` is missing. /// /// # Examples /// /// ``` /// use magnus::{value::Qnil, Error, RHash, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let hash: RHash = ruby.eval(r#"hash = {"answer" => 42}"#)?; /// assert_eq!(hash.delete::<_, i64>("answer")?, 42); /// assert!(hash.delete::<_, Qnil>("answer").is_ok()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn delete(self, key: T) -> Result where T: IntoValue, U: TryConvert, { let key = Ruby::get_with(self).into_value(key); protect(|| unsafe { Value::new(rb_hash_delete(self.as_rb_value(), key.as_rb_value())) }) .and_then(TryConvert::try_convert) } /// Removes all entries from `self`. /// /// Errors if `self` is frozen. /// /// # Examples /// /// ``` /// use magnus::{Error, RHash, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let hash: RHash = ruby.eval(r#"{"answer" => 42}"#)?; /// assert!(!hash.is_empty()); /// hash.clear()?; /// assert!(hash.is_empty()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn clear(self) -> Result<(), Error> { protect(|| unsafe { Value::new(rb_hash_clear(self.as_rb_value())) })?; Ok(()) } /// Run `func` for each key/value pair in `self`. /// /// The result of `func` is checked on each call, when it is /// [`ForEach::Continue`] the iteration will continue, [`ForEach::Stop`] /// will cause the iteration to stop, and [`ForEach::Delete`] will remove /// the key/value pair from `self` and then continue iteration. /// /// Returing an error from `func` behaves like [`ForEach::Stop`]. /// /// # Examples /// /// ``` /// use magnus::{r_hash::ForEach, Error, RHash, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let hash: RHash = ruby.eval(r#"{"foo" => 1, "bar" => 2, "baz" => 4, "qux" => 8}"#)?; /// let mut found = None; /// hash.foreach(|key: String, value: i64| { /// if value > 3 { /// found = Some(key); /// Ok(ForEach::Stop) /// } else { /// Ok(ForEach::Continue) /// } /// })?; /// assert_eq!(found, Some(String::from("baz"))); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn foreach(self, mut func: F) -> Result<(), Error> where F: FnMut(K, V) -> Result, K: TryConvert, V: TryConvert, { unsafe extern "C" fn iter(key: VALUE, value: VALUE, arg: VALUE) -> c_int where F: FnMut(K, V) -> Result, K: TryConvert, V: TryConvert, { let closure = &mut *(arg as *mut F); closure.call_handle_error(Value::new(key), Value::new(value)) as c_int } unsafe { let arg = &mut func as *mut F as VALUE; protect(|| { let fptr = iter:: as unsafe extern "C" fn(VALUE, VALUE, VALUE) -> c_int; rb_hash_foreach(self.as_rb_value(), Some(fptr), arg); Ruby::get_with(self).qnil() })?; } Ok(()) } /// Return `self` converted to a Rust [`HashMap`]. /// /// This will only convert to a map of 'owned' Rust native types. The types /// representing Ruby objects can not be stored in a heap-allocated /// datastructure like a [`HashMap`] as they are hidden from the mark phase /// of Ruby's garbage collector, and thus may be prematurely garbage /// collected in the following sweep phase. /// /// Errors if the conversion of any key or value fails. /// /// # Examples /// /// ``` /// use std::collections::HashMap; /// /// use magnus::{Error, RHash, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let r_hash: RHash = ruby.eval(r#"{"answer" => 42}"#)?; /// let mut hash_map = HashMap::new(); /// hash_map.insert(String::from("answer"), 42); /// assert_eq!(r_hash.to_hash_map()?, hash_map); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn to_hash_map(self) -> Result, Error> where K: TryConvertOwned + Eq + Hash, V: TryConvertOwned, { let mut map = HashMap::new(); self.foreach(|key, value| { map.insert(key, value); Ok(ForEach::Continue) })?; Ok(map) } /// Convert `self` to a Rust vector of key/value pairs. /// /// This will only convert to a map of 'owned' Rust native types. The types /// representing Ruby objects can not be stored in a heap-allocated /// datastructure like a [`Vec`] as they are hidden from the mark phase /// of Ruby's garbage collector, and thus may be prematurely garbage /// collected in the following sweep phase. /// /// Errors if the conversion of any key or value fails. /// /// # Examples /// /// ``` /// use magnus::{Error, RHash, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let r_hash: RHash = ruby.eval(r#"{"answer" => 42}"#)?; /// assert_eq!(r_hash.to_vec()?, vec![(String::from("answer"), 42)]); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn to_vec(self) -> Result, Error> where K: TryConvertOwned, V: TryConvertOwned, { let mut vec = Vec::with_capacity(self.len()); self.foreach(|key, value| { vec.push((key, value)); Ok(ForEach::Continue) })?; Ok(vec) } /// Return the number of entries in `self` as a Ruby [`Fixnum`]. /// /// # Examples /// /// ``` /// use magnus::{Error, RHash, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let hash: RHash = ruby.eval(r#"{"foo" => 1, "bar" => 2, "baz" => 4}"#)?; /// assert_eq!(hash.size().to_i64(), 3); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn size(self) -> Fixnum { unsafe { Fixnum::from_rb_value_unchecked(rb_hash_size(self.as_rb_value())) } } /// Return the number of entries in `self` as a Rust [`usize`]. /// /// # Examples /// /// ``` /// use magnus::{Error, RHash, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let hash: RHash = ruby.eval(r#"{"foo" => 1, "bar" => 2, "baz" => 4}"#)?; /// assert_eq!(hash.len(), 3); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn len(self) -> usize { unsafe { rb_hash_size_num(self.as_rb_value()) as usize } } /// Return whether self contains any entries or not. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let hash = ruby.hash_new(); /// assert!(hash.is_empty()); /// hash.aset("answer", 42)?; /// assert!(!hash.is_empty()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn is_empty(self) -> bool { self.len() == 0 } } impl fmt::Display for RHash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for RHash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for RHash { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl IntoValue for HashMap where K: IntoValueFromNative, V: IntoValueFromNative, { fn into_value_with(self, handle: &Ruby) -> Value { let hash = handle.hash_new(); for (k, v) in self { let _ = hash.aset(k, v); } hash.into_value_with(handle) } } unsafe impl IntoValueFromNative for HashMap where K: IntoValueFromNative, V: IntoValueFromNative, { } #[cfg(feature = "old-api")] impl FromIterator<(K, V)> for RHash where K: IntoValue, V: IntoValue, { fn from_iter(iter: I) -> Self where I: IntoIterator, { get_ruby!().hash_from_iter(iter) } } impl Object for RHash {} unsafe impl private::ReprValue for RHash {} impl ReprValue for RHash {} impl TryConvert for RHash { fn try_convert(val: Value) -> Result { debug_assert_value!(val); if let Some(v) = Self::from_value(val) { return Ok(v); } unsafe { protect(|| Value::new(rb_check_hash_type(val.as_rb_value()))).and_then(|res| { Self::from_value(res).ok_or_else(|| { Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into Hash", val.class()), ) }) }) } } } magnus-0.7.1/src/r_match.rs000064400000000000000000000227311046102023000137020ustar 00000000000000use std::{fmt, os::raw::c_int}; use rb_sys::{ rb_reg_backref_number, rb_reg_last_match, rb_reg_match_last, rb_reg_match_post, rb_reg_match_pre, rb_reg_nth_defined, rb_reg_nth_match, ruby_value_type, VALUE, }; use crate::{ error::{protect, Error}, into_value::IntoValue, object::Object, r_string::{IntoRString, RString}, try_convert::TryConvert, value::{ private::{self, ReprValue as _}, NonZeroValue, ReprValue, Value, }, Ruby, }; /// A Value pointer to a RMatch struct, Ruby's internal representation of the /// MatchData returned from a regex match. /// /// See the [`ReprValue`] and [`Object`] traits for additional methods /// available on this type. #[derive(Clone, Copy)] #[repr(transparent)] pub struct RMatch(NonZeroValue); impl RMatch { /// Return `Some(RMatch)` if `val` is a `RMatch`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, RMatch}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(RMatch::from_value(eval(r#""foo".match(/o/)"#).unwrap()).is_some()); /// assert!(RMatch::from_value(eval(r#""o""#).unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { (val.rb_type() == ruby_value_type::RUBY_T_MATCH) .then(|| Self(NonZeroValue::new_unchecked(val))) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } /// Returns whether the `n`th capture group is set. /// /// Returns `Some(true)` when there is an `n`th capture and it has a value. /// Returns `Some(false)` when there is an `n`th capture but it is empty. /// Returns `None` when there is no `n`th capture. /// /// This function is similar to [`nth_match`](Self::nth_match), but can be /// used to avoid allocating a Ruby string if the value of the capture is /// not needed. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let regexp = ruby.reg_new(".([a-z])([a-z]*)([0-9])?", Default::default())?; /// regexp.reg_match("ex")?; /// let match_data = ruby.backref_get().unwrap(); /// // 0th group is the whole match /// assert_eq!(match_data.nth_defined(0), Some(true)); /// // the `([a-z])` group /// assert_eq!(match_data.nth_defined(1), Some(true)); /// // the `([a-z]*)` group /// assert_eq!(match_data.nth_defined(2), Some(true)); /// // the `([0-9])?` group /// assert_eq!(match_data.nth_defined(3), Some(false)); /// // no 4th group /// assert_eq!(match_data.nth_defined(4), None); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn nth_defined(self, n: isize) -> Option { let value = unsafe { Value::new(rb_reg_nth_defined(n as c_int, self.as_rb_value())) }; Option::::try_convert(value).unwrap() // infallible } /// Returns the string captured by the `n`th capture group. /// /// Returns `None` when there is no `n`th capture. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let regexp = ruby.reg_new(".([a-z])([a-z]*)([0-9])?", Default::default())?; /// regexp.reg_match("ex")?; /// let match_data = ruby.backref_get().unwrap(); /// // 0th group is the whole match /// assert_eq!( /// match_data.nth_match(0).map(|s| s.to_string().unwrap()), /// Some(String::from("ex")) /// ); /// // the `([a-z])` group /// assert_eq!( /// match_data.nth_match(1).map(|s| s.to_string().unwrap()), /// Some(String::from("x")) /// ); /// // the `([a-z]*)` group /// assert_eq!( /// match_data.nth_match(2).map(|s| s.to_string().unwrap()), /// Some(String::from("")) /// ); /// // the `([0-9])?` group /// assert_eq!( /// match_data.nth_match(3).map(|s| s.to_string().unwrap()), /// None /// ); /// // no 4th group /// assert_eq!( /// match_data.nth_match(4).map(|s| s.to_string().unwrap()), /// None /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn nth_match(self, n: isize) -> Option { let value = unsafe { Value::new(rb_reg_nth_match(n as c_int, self.as_rb_value())) }; (!value.is_nil()).then(|| unsafe { RString::from_rb_value_unchecked(value.as_rb_value()) }) } /// Returns the index for the named capture group. /// /// Returns `Err` if there's is no named capture group with the given name. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let regexp = ruby.reg_new("Hello, (?.*)!", Default::default())?; /// regexp.reg_match("Hello, World!")?; /// let match_data = ruby.backref_get().unwrap(); /// assert_eq!(match_data.backref_number("subject")?, 1); /// assert!(match_data.backref_number("foo").is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn backref_number(self, name: T) -> Result where T: IntoRString, { let handle = Ruby::get_with(self); let name = name.into_r_string_with(&handle); let mut n = 0; protect(|| unsafe { n = rb_reg_backref_number(self.as_rb_value(), name.as_rb_value()) as usize; handle.qnil() })?; Ok(n) } /// Returns the string matched. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let regexp = ruby.reg_new("b(.)r", Default::default())?; /// regexp.reg_match("foo bar baz")?; /// /// let match_data = ruby.backref_get().unwrap(); /// assert_eq!(match_data.matched().to_string()?, "bar"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn matched(self) -> RString { unsafe { RString::from_rb_value_unchecked(rb_reg_last_match(self.as_rb_value())) } } /// Returns the string before the segment matched. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let regexp = ruby.reg_new("b(.)r", Default::default())?; /// regexp.reg_match("foo bar baz")?; /// /// let match_data = ruby.backref_get().unwrap(); /// assert_eq!(match_data.pre().to_string()?, "foo "); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn pre(self) -> RString { unsafe { RString::from_rb_value_unchecked(rb_reg_match_pre(self.as_rb_value())) } } /// Returns the string after the segment matched. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let regexp = ruby.reg_new("b(.)r", Default::default())?; /// regexp.reg_match("foo bar baz")?; /// /// let match_data = ruby.backref_get().unwrap(); /// assert_eq!(match_data.post().to_string()?, " baz"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn post(self) -> RString { unsafe { RString::from_rb_value_unchecked(rb_reg_match_post(self.as_rb_value())) } } /// Returns the last capture. /// /// Returns `None` if there are no capture groups. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let regexp = ruby.reg_new("(.)oo b(.)r ba(.)", Default::default())?; /// regexp.reg_match("foo bar baz")?; /// /// let match_data = ruby.backref_get().unwrap(); /// assert_eq!(match_data.last().unwrap().to_string()?, "z"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn last(self) -> Option { let value = unsafe { Value::new(rb_reg_match_last(self.as_rb_value())) }; (!value.is_nil()).then(|| unsafe { RString::from_rb_value_unchecked(value.as_rb_value()) }) } } impl fmt::Display for RMatch { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for RMatch { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for RMatch { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl Object for RMatch {} unsafe impl private::ReprValue for RMatch {} impl ReprValue for RMatch {} impl TryConvert for RMatch { fn try_convert(val: Value) -> Result { Self::from_value(val).ok_or_else(|| { Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into MatchData", unsafe { val.classname() },), ) }) } } magnus-0.7.1/src/r_object.rs000064400000000000000000000045071046102023000140550ustar 00000000000000use std::fmt; use rb_sys::ruby_value_type; use crate::{ error::Error, into_value::IntoValue, object::Object, try_convert::TryConvert, value::{ private::{self, ReprValue as _}, NonZeroValue, ReprValue, Value, }, Ruby, }; /// A Value pointer to a RObject struct, Ruby's internal representation of /// generic objects, not covered by the other R* types. /// /// See the [`ReprValue`] and [`Object`] traits for additional methods /// available on this type. #[derive(Clone, Copy)] #[repr(transparent)] pub struct RObject(NonZeroValue); impl RObject { /// Return `Some(RObject)` if `val` is a `RObject`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, RObject}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(RObject::from_value(eval("Object.new").unwrap()).is_some()); /// /// // many built-in types have specialised implementations and are not /// // RObjects /// assert!(RObject::from_value(eval(r#""example""#).unwrap()).is_none()); /// assert!(RObject::from_value(eval("1").unwrap()).is_none()); /// assert!(RObject::from_value(eval("[]").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { (val.rb_type() == ruby_value_type::RUBY_T_OBJECT) .then(|| Self(NonZeroValue::new_unchecked(val))) } } } impl fmt::Display for RObject { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for RObject { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for RObject { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl Object for RObject {} unsafe impl private::ReprValue for RObject {} impl ReprValue for RObject {} impl TryConvert for RObject { fn try_convert(val: Value) -> Result { Self::from_value(val).ok_or_else(|| { Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into Object", unsafe { val.classname() },), ) }) } } magnus-0.7.1/src/r_rational.rs000064400000000000000000000123071046102023000144150ustar 00000000000000use std::{fmt, num::NonZeroI64}; use rb_sys::{rb_rational_den, rb_rational_new, rb_rational_num, ruby_value_type, VALUE}; use crate::{ error::Error, integer::Integer, into_value::IntoValue, numeric::Numeric, try_convert::TryConvert, value::{ private::{self, ReprValue as _}, NonZeroValue, ReprValue, Value, }, Ruby, }; /// # `RRational` /// /// Functions that can be used to create Ruby `Rational`s. /// /// See also the [`RRational`] type. impl Ruby { /// Create a new `RRational`. /// /// # Examples /// /// ``` /// use std::num::NonZeroI64; /// /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let rational = ruby.rational_new(2, NonZeroI64::new(4).unwrap()); /// assert_eq!(rational.to_string(), "1/2"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn rational_new(&self, num: i64, den: NonZeroI64) -> RRational { let num = self.into_value(num); let den = self.into_value(den.get()); unsafe { RRational::from_rb_value_unchecked(rb_rational_new( num.as_rb_value(), den.as_rb_value(), )) } } } /// A Value pointer to a RRational struct, Ruby's internal representation of /// rational numbers. /// /// See the [`ReprValue`] trait for additional methods available on this type. /// See [`Ruby`](Ruby#rrational) for methods to create an `RRational`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct RRational(NonZeroValue); impl RRational { /// Return `Some(RRational)` if `val` is a `RRational`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, RRational}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(RRational::from_value(eval("1/2r").unwrap()).is_some()); /// assert!(RRational::from_value(eval("0.5").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { (val.rb_type() == ruby_value_type::RUBY_T_RATIONAL) .then(|| Self(NonZeroValue::new_unchecked(val))) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } /// Create a new `RRational`. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::rational_new`] for /// the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use std::num::NonZeroI64; /// /// use magnus::RRational; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let rational = RRational::new(2, NonZeroI64::new(4).unwrap()); /// assert_eq!(rational.to_string(), "1/2"); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::rational_new` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn new(num: i64, den: NonZeroI64) -> Self { get_ruby!().rational_new(num, den) } /// Returns `self`'s numerator. /// /// # Examples /// /// ``` /// use std::num::NonZeroI64; /// /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let rational = ruby.rational_new(6, NonZeroI64::new(9).unwrap()); /// assert_eq!(rational.num().to_i64()?, 2); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn num(self) -> Integer { unsafe { Integer::from_rb_value_unchecked(rb_rational_num(self.as_rb_value())) } } /// Returns `self`'s denominator. /// /// # Examples /// /// ``` /// use std::num::NonZeroI64; /// /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let rational = ruby.rational_new(6, NonZeroI64::new(9).unwrap()); /// assert_eq!(rational.den().to_i64()?, 3); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn den(self) -> Integer { unsafe { Integer::from_rb_value_unchecked(rb_rational_den(self.as_rb_value())) } } } impl fmt::Display for RRational { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for RRational { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for RRational { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } unsafe impl private::ReprValue for RRational {} impl Numeric for RRational {} impl ReprValue for RRational {} impl TryConvert for RRational { fn try_convert(val: Value) -> Result { Self::from_value(val).ok_or_else(|| { Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into Rational", unsafe { val.classname() },), ) }) } } magnus-0.7.1/src/r_regexp.rs000064400000000000000000000330551046102023000141010ustar 00000000000000//! Types for working with Ruby’s Regexp class. use std::{ fmt, os::raw::{c_char, c_int, c_long, c_uint}, }; use rb_sys::{ rb_enc_reg_new, rb_reg_match, rb_reg_new_str, rb_reg_options, ruby_value_type, VALUE, }; use crate::{ encoding::EncodingCapable, error::{protect, Error}, into_value::IntoValue, r_string::{IntoRString, RString}, try_convert::TryConvert, value::{ private::{self, ReprValue as _}, NonZeroValue, ReprValue, Value, }, Ruby, }; /// # `RRegexp` /// /// Functions that can be used to create Ruby `Regexp`s. /// /// See also the [`RRegexp`] type. impl Ruby { /// Create a new `Regexp` from the Rust string `pattern`. /// /// The encoding of the Ruby regexp will be UTF-8. /// /// # Examples /// /// ``` /// use magnus::{r_regexp::Opts, rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let regexp = ruby.reg_new("foo", Opts::new().ignorecase())?; /// rb_assert!(ruby, r#"regexp == /foo/i"#, regexp); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn reg_new(&self, pattern: &str, opts: Opts) -> Result { protect(|| unsafe { RRegexp::from_rb_value_unchecked(rb_enc_reg_new( pattern.as_ptr() as *const c_char, pattern.len() as c_long, self.utf8_encoding().as_ptr(), opts.0 as c_int, )) }) } } /// A Value pointer to a RRegexp struct, Ruby's internal representation of /// regular expressions. /// /// See the [`ReprValue`] trait for additional methods available on this type. /// See [`Ruby`](Ruby#rregexp) for methods to create an `RRegexp`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct RRegexp(NonZeroValue); impl RRegexp { /// Return `Some(RRegexp)` if `val` is a `RRegexp`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, RRegexp}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(RRegexp::from_value(eval("/f(.)o/").unwrap()).is_some()); /// assert!(RRegexp::from_value(eval(r#""f*""#).unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { (val.rb_type() == ruby_value_type::RUBY_T_REGEXP) .then(|| Self(NonZeroValue::new_unchecked(val))) } } #[inline] unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } /// Create a new `Regexp` from the Rust string `pattern`. /// /// The encoding of the Ruby regexp will be UTF-8. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::reg_new`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{r_regexp::Opts, rb_assert, RRegexp}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let regexp = RRegexp::new("foo", Opts::new().ignorecase()).unwrap(); /// rb_assert!(r#"regexp == /foo/i"#, regexp); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::reg_new` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn new(pattern: &str, opts: Opts) -> Result { get_ruby!().reg_new(pattern, opts) } /// Create a new `Regexp` from the Ruby string `pattern`. /// /// # Examples /// /// ``` /// use magnus::{r_regexp::Opts, rb_assert, Error, RRegexp, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let regexp = RRegexp::new_str(ruby.str_new("foo"), Opts::new().ignorecase())?; /// rb_assert!(ruby, r#"regexp == /foo/i"#, regexp); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn new_str(pattern: RString, opts: Opts) -> Result { protect(|| unsafe { Self::from_rb_value_unchecked(rb_reg_new_str(pattern.as_rb_value(), opts.0 as c_int)) }) } /// Returns the index (in characters) of the first match in `s`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let regexp = ruby.reg_new("x", Default::default())?; /// assert_eq!(regexp.reg_match("text")?, Some(2)); /// assert_eq!(regexp.reg_match("test")?, None); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn reg_match(self, s: T) -> Result, Error> where T: IntoRString, { let s = s.into_r_string_with(&Ruby::get_with(self)); protect(|| unsafe { Value::new(rb_reg_match(self.as_rb_value(), s.as_rb_value())) }) .and_then(TryConvert::try_convert) } /// Returns the options set for `self`. /// /// # Examples /// /// ``` /// use magnus::{Error, RRegexp, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let regexp: RRegexp = ruby.eval("/x/i").unwrap(); /// assert!(regexp.options().is_ignorecase()); /// assert!(!regexp.options().is_multiline()); /// /// let regexp: RRegexp = ruby.eval("/x/m").unwrap(); /// assert!(!regexp.options().is_ignorecase()); /// assert!(regexp.options().is_multiline()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn options(self) -> Opts { unsafe { Opts(rb_reg_options(self.as_rb_value()) as c_uint) } } } impl fmt::Display for RRegexp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for RRegexp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl EncodingCapable for RRegexp {} impl IntoValue for RRegexp { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } unsafe impl private::ReprValue for RRegexp {} impl ReprValue for RRegexp {} impl TryConvert for RRegexp { fn try_convert(val: Value) -> Result { Self::from_value(val).ok_or_else(|| { Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into Regexp", unsafe { val.classname() },), ) }) } } /// Options for creating [`RRegexp`]. #[derive(Clone, Copy)] pub struct Opts(c_uint); impl Opts { /// Ignore case. pub const IGNORECASE: Self = Self::new().ignorecase(); /// Ignore whitespace and comments in the pattern. pub const EXTEND: Self = Self::new().extend(); /// Treat a newline as a character matched by `.` pub const MULTILINE: Self = Self::new().multiline(); /// Create a new blank regexp `Opts`. pub const fn new() -> Self { Self(rb_sys::ONIG_OPTION_NONE) } /// Ignore case. /// /// Equivalent to `/pattern/i` in Ruby. pub const fn ignorecase(self) -> Self { Self(self.0 | rb_sys::ONIG_OPTION_IGNORECASE) } /// Ignore whitespace and comments in the pattern. /// /// Equivalent to `/pattern/x` in Ruby. pub const fn extend(self) -> Self { Self(self.0 | rb_sys::ONIG_OPTION_EXTEND) } /// Treat a newline as a character matched by `.` /// /// Equivalent to `/pattern/m` in Ruby. pub const fn multiline(self) -> Self { Self(self.0 | rb_sys::ONIG_OPTION_MULTILINE) } // undocumented in Ruby #[allow(missing_docs)] pub const fn dotall(self) -> Self { Self(self.0 | rb_sys::ONIG_OPTION_DOTALL) } // undocumented in Ruby #[allow(missing_docs)] pub const fn singleline(self) -> Self { Self(self.0 | rb_sys::ONIG_OPTION_SINGLELINE) } // undocumented in Ruby #[allow(missing_docs)] pub const fn find_longest(self) -> Self { Self(self.0 | rb_sys::ONIG_OPTION_FIND_LONGEST) } // undocumented in Ruby #[allow(missing_docs)] pub const fn find_not_empty(self) -> Self { Self(self.0 | rb_sys::ONIG_OPTION_FIND_NOT_EMPTY) } // undocumented in Ruby #[allow(missing_docs)] pub const fn negate_singleline(self) -> Self { Self(self.0 | rb_sys::ONIG_OPTION_NEGATE_SINGLELINE) } // undocumented in Ruby #[allow(missing_docs)] pub const fn dont_capture_group(self) -> Self { Self(self.0 | rb_sys::ONIG_OPTION_DONT_CAPTURE_GROUP) } // undocumented in Ruby #[allow(missing_docs)] pub const fn capture_group(self) -> Self { Self(self.0 | rb_sys::ONIG_OPTION_CAPTURE_GROUP) } // undocumented in Ruby #[allow(missing_docs)] pub const fn notbol(self) -> Self { Self(self.0 | rb_sys::ONIG_OPTION_NOTBOL) } // undocumented in Ruby #[allow(missing_docs)] pub const fn noteol(self) -> Self { Self(self.0 | rb_sys::ONIG_OPTION_NOTEOL) } // undocumented in Ruby #[allow(missing_docs)] pub const fn notbos(self) -> Self { Self(self.0 | rb_sys::ONIG_OPTION_NOTBOS) } // undocumented in Ruby #[allow(missing_docs)] pub const fn noteos(self) -> Self { Self(self.0 | rb_sys::ONIG_OPTION_NOTEOS) } // undocumented in Ruby #[allow(missing_docs)] pub const fn ascii_range(self) -> Self { Self(self.0 | rb_sys::ONIG_OPTION_ASCII_RANGE) } // undocumented in Ruby #[allow(missing_docs)] pub const fn posix_bracket_all_range(self) -> Self { Self(self.0 | rb_sys::ONIG_OPTION_POSIX_BRACKET_ALL_RANGE) } // undocumented in Ruby #[allow(missing_docs)] pub const fn word_bound_all_range(self) -> Self { Self(self.0 | rb_sys::ONIG_OPTION_WORD_BOUND_ALL_RANGE) } // undocumented in Ruby #[allow(missing_docs)] pub const fn newline_crlf(self) -> Self { Self(self.0 | rb_sys::ONIG_OPTION_NEWLINE_CRLF) } /// Return `true` if the `ignorecase` option is set, `false` otherwise. pub const fn is_ignorecase(self) -> bool { self.0 & rb_sys::ONIG_OPTION_IGNORECASE != 0 } /// Return `true` if the `extend` option is set, `false` otherwise. pub const fn is_extend(self) -> bool { self.0 & rb_sys::ONIG_OPTION_EXTEND != 0 } /// Return `true` if the `multiline` option is set, `false` otherwise. pub const fn is_multiline(self) -> bool { self.0 & rb_sys::ONIG_OPTION_MULTILINE != 0 } /// Return `true` if the `dotall` option is set, `false` otherwise. pub const fn is_dotall(self) -> bool { self.0 & rb_sys::ONIG_OPTION_DOTALL != 0 } /// Return `true` if the `singleline` option is set, `false` otherwise. pub const fn is_singleline(self) -> bool { self.0 & rb_sys::ONIG_OPTION_SINGLELINE != 0 } /// Return `true` if the `find_longest` option is set, `false` otherwise. pub const fn is_find_longest(self) -> bool { self.0 & rb_sys::ONIG_OPTION_FIND_LONGEST != 0 } /// Return `true` if the `find_not_empty` option is set, `false` otherwise. pub const fn is_find_not_empty(self) -> bool { self.0 & rb_sys::ONIG_OPTION_FIND_NOT_EMPTY != 0 } /// Return `true` if the `negate_singleline` option is set, `false` /// otherwise. pub const fn is_negate_singleline(self) -> bool { self.0 & rb_sys::ONIG_OPTION_NEGATE_SINGLELINE != 0 } /// Return `true` if the `dont_capture_group` option is set, `false` /// otherwise. pub const fn is_dont_capture_group(self) -> bool { self.0 & rb_sys::ONIG_OPTION_DONT_CAPTURE_GROUP != 0 } /// Return `true` if the `capture_group` option is set, `false` otherwise. pub const fn is_capture_group(self) -> bool { self.0 & rb_sys::ONIG_OPTION_CAPTURE_GROUP != 0 } /// Return `true` if the `notbol` option is set, `false` otherwise. pub const fn is_notbol(self) -> bool { self.0 & rb_sys::ONIG_OPTION_NOTBOL != 0 } /// Return `true` if the `noteol` option is set, `false` otherwise. pub const fn is_noteol(self) -> bool { self.0 & rb_sys::ONIG_OPTION_NOTEOL != 0 } /// Return `true` if the `notbos` option is set, `false` otherwise. pub const fn is_notbos(self) -> bool { self.0 & rb_sys::ONIG_OPTION_NOTBOS != 0 } /// Return `true` if the `noteos` option is set, `false` otherwise. pub const fn is_noteos(self) -> bool { self.0 & rb_sys::ONIG_OPTION_NOTEOS != 0 } /// Return `true` if the `ascii_range` option is set, `false` otherwise. pub const fn is_ascii_range(self) -> bool { self.0 & rb_sys::ONIG_OPTION_ASCII_RANGE != 0 } /// Return `true` if the `posix_bracket_all_range` option is set, `false` /// otherwise. pub const fn is_posix_bracket_all_range(self) -> bool { self.0 & rb_sys::ONIG_OPTION_POSIX_BRACKET_ALL_RANGE != 0 } /// Return `true` if the `word_bound_all_range` option is set, `false` /// otherwise. pub const fn is_word_bound_all_range(self) -> bool { self.0 & rb_sys::ONIG_OPTION_WORD_BOUND_ALL_RANGE != 0 } /// Return `true` if the `newline_crlf` option is set, `false` otherwise. pub const fn is_newline_crlf(self) -> bool { self.0 & rb_sys::ONIG_OPTION_NEWLINE_CRLF != 0 } } impl Default for Opts { fn default() -> Self { Self::new() } } impl From for i32 { fn from(val: Opts) -> i32 { val.0 as i32 } } magnus-0.7.1/src/r_string.rs000064400000000000000000002032671046102023000141210ustar 00000000000000//! Types for working with Ruby’s String class. use std::{ borrow::Cow, cmp::Ordering, ffi::CString, fmt, io, iter::Iterator, mem::transmute, os::raw::{c_char, c_long}, path::{Path, PathBuf}, ptr, slice, str, }; #[cfg(ruby_gte_3_0)] use rb_sys::rb_str_to_interned_str; use rb_sys::{ self, rb_enc_str_coderange, rb_enc_str_new, rb_str_buf_append, rb_str_buf_new, rb_str_capacity, rb_str_cat, rb_str_cmp, rb_str_comparable, rb_str_conv_enc, rb_str_drop_bytes, rb_str_dump, rb_str_ellipsize, rb_str_new, rb_str_new_frozen, rb_str_new_shared, rb_str_offset, rb_str_plus, rb_str_replace, rb_str_scrub, rb_str_shared_replace, rb_str_split, rb_str_strlen, rb_str_times, rb_str_to_str, rb_str_update, rb_utf8_str_new, rb_utf8_str_new_static, ruby_coderange_type, ruby_rstring_flags, ruby_value_type, RSTRING_LEN, RSTRING_PTR, VALUE, }; use crate::{ encoding::{Coderange, EncodingCapable, RbEncoding}, error::{protect, Error}, into_value::{IntoValue, IntoValueFromNative}, object::Object, r_array::RArray, try_convert::TryConvert, value::{ private::{self, ReprValue as _}, NonZeroValue, ReprValue, Value, }, Ruby, }; /// # `RString` /// /// Functions that can be used to create Ruby `String`s. /// /// See also the [`RString`] type. impl Ruby { /// Create a new Ruby string from the Rust string `s`. /// /// The encoding of the Ruby string will be UTF-8. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let val = ruby.str_new("example"); /// rb_assert!(ruby, r#"val == "example""#, val); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn str_new(&self, s: &str) -> RString { let len = s.len(); let ptr = s.as_ptr(); unsafe { RString::from_rb_value_unchecked(rb_utf8_str_new(ptr as *const c_char, len as c_long)) } } /// Implementation detail of [`r_string`]. #[doc(hidden)] #[inline] pub unsafe fn str_new_lit(&self, ptr: *const c_char, len: c_long) -> RString { RString::from_rb_value_unchecked(rb_utf8_str_new_static(ptr, len)) } /// Create a new Ruby string with capacity `n`. /// /// The encoding will be set to ASCII-8BIT (aka BINARY). See also /// [`with_capacity`](RString::with_capacity). /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let buf = ruby.str_buf_new(4096); /// buf.cat(&[13, 14, 10, 13, 11, 14, 14, 15]); /// rb_assert!(ruby, r#"buf == "\r\x0E\n\r\v\x0E\x0E\x0F""#, buf); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn str_buf_new(&self, n: usize) -> RString { unsafe { RString::from_rb_value_unchecked(rb_str_buf_new(n as c_long)) } } /// Create a new Ruby string with capacity `n`. /// /// The encoding will be set to UTF-8. See also /// [`buf_new`](RString::buf_new). /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_with_capacity(9); /// s.cat("foo"); /// s.cat("bar"); /// s.cat("baz"); /// rb_assert!(ruby, r#"s == "foobarbaz""#, s); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn str_with_capacity(&self, n: usize) -> RString { let s = self.str_buf_new(n); s.enc_associate(self.utf8_encindex()).unwrap(); s } /// Create a new Ruby string from the Rust slice `s`. /// /// The encoding of the Ruby string will be set to ASCII-8BIT (aka BINARY). /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let buf = ruby.str_from_slice(&[13, 14, 10, 13, 11, 14, 14, 15]); /// rb_assert!(ruby, r#"buf == "\r\x0E\n\r\v\x0E\x0E\x0F""#, buf); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn str_from_slice(&self, s: &[u8]) -> RString { let len = s.len(); let ptr = s.as_ptr(); unsafe { RString::from_rb_value_unchecked(rb_str_new(ptr as *const c_char, len as c_long)) } } /// Create a new Ruby string from the value `s` with the encoding `enc`. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let val = ruby.enc_str_new("example", ruby.usascii_encoding()); /// rb_assert!(ruby, r#"val == "example""#, val); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let val = ruby.enc_str_new([255, 128, 128], ruby.ascii8bit_encoding()); /// rb_assert!( /// ruby, /// r#"val == "\xFF\x80\x80".force_encoding("BINARY")"#, /// val /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn enc_str_new(&self, s: T, enc: E) -> RString where T: AsRef<[u8]>, E: Into, { let s = s.as_ref(); let len = s.len(); let ptr = s.as_ptr(); unsafe { RString::from_rb_value_unchecked(rb_enc_str_new( ptr as *const c_char, len as c_long, enc.into().as_ptr(), )) } } /// Create a new Ruby string from the Rust char `c`. /// /// The encoding of the Ruby string will be UTF-8. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let c = ruby.str_from_char('a'); /// rb_assert!(ruby, r#"c == "a""#, c); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let c = ruby.str_from_char('🦀'); /// rb_assert!(ruby, r#"c == "🦀""#, c); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn str_from_char(&self, c: char) -> RString { let mut buf = [0; 4]; self.str_new(c.encode_utf8(&mut buf[..])) } /// Create a new Ruby string containing the codepoint `code` in the /// encoding `enc`. /// /// The encoding of the Ruby string will be the passed encoding `enc`. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let c = ruby.chr(97, ruby.usascii_encoding())?; /// rb_assert!(ruby, r#"c == "a""#, c); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let c = ruby.chr(129408, ruby.utf8_encoding())?; /// rb_assert!(ruby, r#"c == "🦀""#, c); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn chr(&self, code: u32, enc: T) -> Result where T: Into, { enc.into().chr(code) } } /// A Value pointer to a RString struct, Ruby's internal representation of /// strings. /// /// See the [`ReprValue`] and [`Object`] traits for additional methods /// available on this type. See [`Ruby`](Ruby#rstring) for methods to create an /// `RString`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct RString(NonZeroValue); impl RString { /// Return `Some(RString)` if `val` is a `RString`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(RString::from_value(eval(r#""example""#).unwrap()).is_some()); /// assert!(RString::from_value(eval(":example").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { (val.rb_type() == ruby_value_type::RUBY_T_STRING) .then(|| Self(NonZeroValue::new_unchecked(val))) } } pub(crate) fn ref_from_value(val: &Value) -> Option<&Self> { unsafe { (val.rb_type() == ruby_value_type::RUBY_T_STRING) .then(|| &*(val as *const _ as *const RString)) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } /// Create a new Ruby string from the Rust string `s`. /// /// The encoding of the Ruby string will be UTF-8. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::str_new`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{rb_assert, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let val = RString::new("example"); /// rb_assert!(r#"val == "example""#, val); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::str_new` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn new(s: &str) -> Self { get_ruby!().str_new(s) } /// Implementation detail of [`r_string`]. #[doc(hidden)] #[inline] pub unsafe fn new_lit(ptr: *const c_char, len: c_long) -> Self { get_ruby!().str_new_lit(ptr, len) } /// Create a new Ruby string with capacity `n`. /// /// The encoding will be set to ASCII-8BIT (aka BINARY). See also /// [`with_capacity`](RString::with_capacity). /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::str_buf_new`] for /// the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{rb_assert, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let buf = RString::buf_new(4096); /// buf.cat(&[13, 14, 10, 13, 11, 14, 14, 15]); /// rb_assert!(r#"buf == "\r\x0E\n\r\v\x0E\x0E\x0F""#, buf); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::str_buf_new` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn buf_new(n: usize) -> Self { get_ruby!().str_buf_new(n) } /// Create a new Ruby string with capacity `n`. /// /// The encoding will be set to UTF-8. See also /// [`buf_new`](RString::buf_new). /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::str_with_capacity`] for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{rb_assert, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let s = RString::with_capacity(9); /// s.cat("foo"); /// s.cat("bar"); /// s.cat("baz"); /// rb_assert!(r#"s == "foobarbaz""#, s); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::str_with_capacity` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn with_capacity(n: usize) -> Self { get_ruby!().str_with_capacity(n) } /// Create a new Ruby string from the Rust slice `s`. /// /// The encoding of the Ruby string will be set to ASCII-8BIT (aka BINARY). /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::str_from_slice`] /// for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{rb_assert, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let buf = RString::from_slice(&[13, 14, 10, 13, 11, 14, 14, 15]); /// rb_assert!(r#"buf == "\r\x0E\n\r\v\x0E\x0E\x0F""#, buf); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::str_from_slice` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn from_slice(s: &[u8]) -> Self { get_ruby!().str_from_slice(s) } /// Create a new Ruby string from the value `s` with the encoding `enc`. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::enc_str_new`] for /// the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{encoding::RbEncoding, rb_assert, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let val = RString::enc_new("example", RbEncoding::usascii()); /// rb_assert!(r#"val == "example""#, val); /// ``` /// /// ``` /// # #![allow(deprecated)] /// use magnus::{encoding::RbEncoding, rb_assert, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let val = RString::enc_new([255, 128, 128], RbEncoding::ascii8bit()); /// rb_assert!(r#"val == "\xFF\x80\x80".force_encoding("BINARY")"#, val); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::enc_str_new` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn enc_new(s: T, enc: E) -> Self where T: AsRef<[u8]>, E: Into, { get_ruby!().enc_str_new(s, enc) } /// Create a new Ruby string from the Rust char `c`. /// /// The encoding of the Ruby string will be UTF-8. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::str_from_char`] /// for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{rb_assert, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let c = RString::from_char('a'); /// rb_assert!(r#"c == "a""#, c); /// ``` /// /// ``` /// # #![allow(deprecated)] /// use magnus::{rb_assert, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let c = RString::from_char('🦀'); /// rb_assert!(r#"c == "🦀""#, c); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::str_from_char` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn from_char(c: char) -> Self { get_ruby!().str_from_char(c) } /// Create a new Ruby string containing the codepoint `code` in the /// encoding `enc`. /// /// The encoding of the Ruby string will be the passed encoding `enc`. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::chr`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{encoding::RbEncoding, rb_assert, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let c = RString::chr(97, RbEncoding::usascii()).unwrap(); /// rb_assert!(r#"c == "a""#, c); /// ``` /// /// ``` /// # #![allow(deprecated)] /// use magnus::{encoding::RbEncoding, rb_assert, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let c = RString::chr(129408, RbEncoding::utf8()).unwrap(); /// rb_assert!(r#"c == "🦀""#, c); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::chr` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn chr(code: u32, enc: T) -> Result where T: Into, { get_ruby!().chr(code, enc) } /// Create a new Ruby string that shares the same backing data as `s`. /// /// Both string objects will point at the same underlying data until one is /// modified, and only then will the data be duplicated. This operation is /// cheep, and useful for cases where you may need to modify a string, but /// don't want to mutate a value passed to your function. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, RString, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("example"); /// let dup = RString::new_shared(s); /// rb_assert!(ruby, "s == dup", s, dup); /// // mutating one doesn't mutate both /// dup.cat("foo"); /// rb_assert!(ruby, "s != dup", s, dup); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn new_shared(s: Self) -> Self { unsafe { Self::from_rb_value_unchecked(rb_str_new_shared(s.as_rb_value())) } } /// Create a new Ruby string that is a frozen copy of `s`. /// /// This can be used to get a copy of a string that is guranteed not to be /// modified while you are referencing it. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, RString, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let orig = ruby.str_new("example"); /// let frozen = RString::new_frozen(orig); /// rb_assert!(ruby, r#"frozen == "example""#, frozen); /// // mutating original doesn't impact the frozen copy /// orig.cat("foo"); /// rb_assert!(ruby, r#"frozen == "example""#, frozen); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn new_frozen(s: Self) -> Self { unsafe { Self::from_rb_value_unchecked(rb_str_new_frozen(s.as_rb_value())) } } /// Return `self` as a slice of bytes. /// /// # Safety /// /// This is directly viewing memory owned and managed by Ruby. Ruby may /// modify or free the memory backing the returned slice, the caller must /// ensure this does not happen. /// /// Ruby must not be allowed to garbage collect or modify `self` while a /// refrence to the slice is held. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("example"); /// // safe as we don't give Ruby the chance to mess with the string while /// // we hold a refrence to the slice. /// unsafe { assert_eq!(s.as_slice(), [101, 120, 97, 109, 112, 108, 101]) }; /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub unsafe fn as_slice(&self) -> &[u8] { self.as_slice_unconstrained() } unsafe fn as_slice_unconstrained<'a>(self) -> &'a [u8] { debug_assert_value!(self); slice::from_raw_parts( RSTRING_PTR(self.as_rb_value()) as *const u8, RSTRING_LEN(self.as_rb_value()) as _, ) } /// Return an iterator over `self`'s codepoints. /// /// # Safety /// /// The returned iterator references memory owned and managed by Ruby. Ruby /// may modify or free that memory, the caller must ensure this does not /// happen at any time while still holding a reference to the iterator. /// /// # Examples /// /// ``` /// use magnus::{Error, RString, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("🦀 café"); /// /// let codepoints = unsafe { /// // ensure string isn't mutated during iteration by creating a /// // frozen copy and iterating over that /// let f = RString::new_frozen(s); /// f.codepoints().collect::, Error>>()? /// }; /// /// assert_eq!(codepoints, [129408, 32, 99, 97, 102, 233]); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub unsafe fn codepoints(&self) -> Codepoints { Codepoints { slice: self.as_slice(), encoding: self.enc_get().into(), } } /// Return an iterator over `self`'s chars as slices of bytes. /// /// # Safety /// /// The returned iterator references memory owned and managed by Ruby. Ruby /// may modify or free that memory, the caller must ensure this does not /// happen at any time while still holding a reference to the iterator. /// /// # Examples /// /// ``` /// use magnus::{Error, RString, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("🦀 café"); /// /// // ensure string isn't mutated during iteration by creating a frozen /// // copy and iterating over that /// let f = RString::new_frozen(s); /// let codepoints = unsafe { f.char_bytes().collect::>() }; /// /// assert_eq!( /// codepoints, /// [ /// &[240, 159, 166, 128][..], /// &[32], /// &[99], /// &[97], /// &[102], /// &[195, 169] /// ] /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub unsafe fn char_bytes(&self) -> CharBytes { CharBytes { slice: self.as_slice(), encoding: self.enc_get().into(), } } /// Converts a character offset to a byte offset. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("🌊🦀🏝️"); /// assert_eq!(s.offset(1), 4); /// assert_eq!(s.offset(2), 8); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn offset(self, pos: usize) -> usize { unsafe { rb_str_offset(self.as_rb_value(), pos as c_long) as usize } } /// Returns true if the encoding for this string is UTF-8 or US-ASCII, /// false otherwise. /// /// The encoding on a Ruby String is just a label, it provides no guarantee /// that the String really is valid UTF-8. /// /// # Examples /// /// ``` /// use magnus::{eval, Error, RString, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s: RString = eval!(ruby, r#""café""#)?; /// assert!(s.is_utf8_compatible_encoding()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{eval, Error, RString, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s: RString = eval!(ruby, r#""café".encode("ISO-8859-1")"#)?; /// assert!(!s.is_utf8_compatible_encoding()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn is_utf8_compatible_encoding(self) -> bool { let handle = Ruby::get_with(self); let encindex = self.enc_get(); // us-ascii is a 100% compatible subset of utf8 encindex == handle.utf8_encindex() || encindex == handle.usascii_encindex() } /// Returns a new string by reencoding `self` from its current encoding to /// the given `enc`. /// /// # Examples /// /// ``` /// use magnus::{eval, Error, RString, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s: RString = eval!(ruby, r#""café".encode("ISO-8859-1")"#)?; /// // safe as we don't give Ruby the chance to mess with the string while /// // we hold a refrence to the slice. /// unsafe { assert_eq!(s.as_slice(), &[99, 97, 102, 233]) }; /// let e = s.conv_enc(ruby.utf8_encoding())?; /// unsafe { assert_eq!(e.as_slice(), &[99, 97, 102, 195, 169]) }; /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn conv_enc(self, enc: T) -> Result where T: Into, { protect(|| unsafe { Self::from_rb_value_unchecked(rb_str_conv_enc( self.as_rb_value(), ptr::null_mut(), enc.into().as_ptr(), )) }) } /// Returns a string omitting 'broken' parts of the string according to its /// encoding. /// /// If `replacement` is `Some(RString)` and 'broken' portion will be /// replaced with that string. When `replacement` is `None` an encoding /// specific default will be used. /// /// If `self` is not 'broken' and no replacement was made, returns /// `Ok(None)`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// // 156 is invalid for utf-8 /// let s = ruby.enc_str_new([156, 57, 57], ruby.utf8_encoding()); /// assert_eq!(s.scrub(None)?.unwrap().to_string()?, "�99"); /// assert_eq!( /// s.scrub(Some(ruby.str_new("?")))?.unwrap().to_string()?, /// "?99" /// ); /// assert_eq!(s.scrub(Some(ruby.str_new("")))?.unwrap().to_string()?, "99"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn scrub(self, replacement: Option) -> Result, Error> { let val = protect(|| unsafe { Value::new(rb_str_scrub( self.as_rb_value(), replacement .map(|r| r.as_rb_value()) .unwrap_or_else(|| Ruby::get_with(self).qnil().as_rb_value()), )) })?; if val.is_nil() { Ok(None) } else { unsafe { Ok(Some(Self(NonZeroValue::new_unchecked(val)))) } } } /// Returns the cached coderange value that describes how `self` relates to /// its encoding. /// /// See also [`enc_coderange_scan`](RString::enc_coderange_scan). /// /// # Examples /// /// ``` /// use magnus::{encoding::Coderange, prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// // Coderange is unknown on creation. /// let s = ruby.str_new("test"); /// assert_eq!(s.enc_coderange(), Coderange::Unknown); /// /// // Methods that operate on the string using the encoding will set the /// // coderange as a side effect. /// let _: usize = s.funcall("length", ())?; /// assert_eq!(s.enc_coderange(), Coderange::SevenBit); /// /// // Operations with two strings with known coderanges will set it /// // appropriately. /// let t = ruby.str_new("🦀"); /// let _: usize = t.funcall("length", ())?; /// assert_eq!(t.enc_coderange(), Coderange::Valid); /// s.buf_append(t)?; /// assert_eq!(s.enc_coderange(), Coderange::Valid); /// /// // Operations that modify the string with an unknown coderange will /// // set the coderange back to unknown. /// s.cat([128]); /// assert_eq!(s.enc_coderange(), Coderange::Unknown); /// /// // Which may leave the string with a broken encoding. /// let _: usize = s.funcall("length", ())?; /// assert_eq!(s.enc_coderange(), Coderange::Broken); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn enc_coderange(self) -> Coderange { unsafe { transmute( (self.r_basic_unchecked().as_ref().flags & ruby_coderange_type::RUBY_ENC_CODERANGE_MASK as VALUE) as u32, ) } } /// Scans `self` to establish its coderange. /// /// If the coderange is already known, simply returns the known value. /// See also [`enc_coderange`](RString::enc_coderange). /// /// # Examples /// /// ``` /// use magnus::{encoding::Coderange, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("test"); /// assert_eq!(s.enc_coderange_scan(), Coderange::SevenBit); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn enc_coderange_scan(self) -> Coderange { unsafe { transmute(rb_enc_str_coderange(self.as_rb_value()) as u32) } } /// Clear `self`'s cached coderange, setting it to `Unknown`. /// /// # Examples /// /// ``` /// use magnus::{encoding::Coderange, prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("🦀"); /// // trigger setting coderange /// let _: usize = s.funcall("length", ())?; /// assert_eq!(s.enc_coderange(), Coderange::Valid); /// /// s.enc_coderange_clear(); /// assert_eq!(s.enc_coderange(), Coderange::Unknown); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn enc_coderange_clear(self) { unsafe { self.r_basic_unchecked().as_mut().flags &= !(ruby_coderange_type::RUBY_ENC_CODERANGE_MASK as VALUE) } } /// Sets `self`'s cached coderange. /// /// Rather than using the method it is recommended to set the coderange to /// `Unknown` with [`enc_coderange_clear`](RString::enc_coderange_clear) /// and let Ruby determine the coderange lazily when needed. /// /// # Safety /// /// This must be set correctly. `SevenBit` if all codepoints are within /// 0..=127, `Valid` if the string is valid for its encoding, or `Broken` /// if it is not. `Unknown` can be set safely with /// [`enc_coderange_clear`](RString::enc_coderange_clear). /// /// # Examples /// /// ``` /// use magnus::{encoding::Coderange, prelude::*, Error, RString, Ruby}; /// /// fn crabbify(ruby: &Ruby, s: RString) -> Result<(), Error> { /// if s.enc_get() != ruby.utf8_encindex() { /// return Err(Error::new( /// ruby.exception_encoding_error(), /// "expected utf-8", /// )); /// } /// let original = s.enc_coderange(); /// // ::cat() will clear the coderange /// s.cat("🦀"); /// // we added a multibyte char, so if we started with `SevenBit` it /// // should be upgraded to `Valid`, and if it was `Valid` it is still /// // `Valid`. /// if original == Coderange::SevenBit || original == Coderange::Valid { /// unsafe { /// s.enc_coderange_set(Coderange::Valid); /// } /// } /// Ok(()) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("test"); /// // trigger setting coderange /// let _: usize = s.funcall("length", ())?; /// /// crabbify(ruby, s)?; /// assert_eq!(s.enc_coderange(), Coderange::Valid); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub unsafe fn enc_coderange_set(self, cr: Coderange) { self.enc_coderange_clear(); self.r_basic_unchecked().as_mut().flags |= cr as VALUE; } /// Returns a Rust `&str` reference to the value of `self`. /// /// Returns `None` if `self`'s encoding is not UTF-8 (or US-ASCII), or if /// the string is not valid UTF-8. /// /// # Safety /// /// This is directly viewing memory owned and managed by Ruby. Ruby may /// modify or free the memory backing the returned str, the caller must /// ensure this does not happen. /// /// Ruby must not be allowed to garbage collect or modify `self` while a /// refrence to the str is held. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("example"); /// // safe as we don't give Ruby the chance to mess with the string while /// // we hold a refrence to the slice. /// unsafe { assert_eq!(s.test_as_str().unwrap(), "example") }; /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub unsafe fn test_as_str(&self) -> Option<&str> { self.test_as_str_unconstrained() } /// Returns a Rust `&str` reference to the value of `self`. /// /// Errors if `self`'s encoding is not UTF-8 (or US-ASCII), or if the /// string is not valid UTF-8. /// /// # Safety /// /// This is directly viewing memory owned and managed by Ruby. Ruby may /// modify or free the memory backing the returned str, the caller must /// ensure this does not happen. /// /// Ruby must not be allowed to garbage collect or modify `self` while a /// refrence to the str is held. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("example"); /// // safe as we don't give Ruby the chance to mess with the string while /// // we hold a refrence to the slice. /// unsafe { assert_eq!(s.as_str()?, "example") }; /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub unsafe fn as_str(&self) -> Result<&str, Error> { self.as_str_unconstrained() } unsafe fn test_as_str_unconstrained<'a>(self) -> Option<&'a str> { let handle = Ruby::get_with(self); let enc = self.enc_get(); let cr = self.enc_coderange_scan(); ((self.is_utf8_compatible_encoding() && (cr == Coderange::SevenBit || cr == Coderange::Valid)) || (enc == handle.ascii8bit_encindex() && cr == Coderange::SevenBit)) .then(|| str::from_utf8_unchecked(self.as_slice_unconstrained())) } unsafe fn as_str_unconstrained<'a>(self) -> Result<&'a str, Error> { self.test_as_str_unconstrained().ok_or_else(|| { let msg: Cow<'static, str> = if self.is_utf8_compatible_encoding() { format!( "expected utf-8, got {}", RbEncoding::from(self.enc_get()).name() ) .into() } else { "invalid byte sequence in UTF-8".into() }; Error::new(Ruby::get_with(self).exception_encoding_error(), msg) }) } /// Returns `self` as a Rust string, ignoring the Ruby encoding and /// dropping any non-UTF-8 characters. If `self` is valid UTF-8 this will /// return a `&str` reference. /// /// # Safety /// /// This may return a direct view of memory owned and managed by Ruby. Ruby /// may modify or free the memory backing the returned str, the caller must /// ensure this does not happen. /// /// Ruby must not be allowed to garbage collect or modify `self` while a /// refrence to the str is held. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("example"); /// // safe as we don't give Ruby the chance to mess with the string while /// // we hold a refrence to the slice. /// unsafe { assert_eq!(s.to_string_lossy(), "example") }; /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[allow(clippy::wrong_self_convention)] pub unsafe fn to_string_lossy(&self) -> Cow<'_, str> { String::from_utf8_lossy(self.as_slice()) } /// Returns `self` as an owned Rust `String`. The Ruby string will be /// reencoded as UTF-8 if required. Errors if the string can not be encoded /// as UTF-8. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("example"); /// assert_eq!(s.to_string()?, "example"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn to_string(self) -> Result { let handle = Ruby::get_with(self); let utf8 = if self.is_utf8_compatible_encoding() { self } else { self.conv_enc(handle.utf8_encoding())? }; str::from_utf8(unsafe { utf8.as_slice() }) .map(ToOwned::to_owned) .map_err(|e| Error::new(handle.exception_encoding_error(), format!("{}", e))) } /// Returns `self` as an owned Rust `Bytes`. /// /// # Examples /// /// ``` /// use bytes::Bytes; /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("example"); /// assert_eq!(s.to_bytes(), Bytes::from("example")); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "bytes")))] #[cfg(feature = "bytes")] pub fn to_bytes(self) -> bytes::Bytes { let vec = unsafe { self.as_slice().to_vec() }; vec.into() } /// Converts `self` to a [`char`]. Errors if the string is more than one /// character or can not be encoded as UTF-8. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("a"); /// assert_eq!(s.to_char()?, 'a'); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn to_char(self) -> Result { let handle = Ruby::get_with(self); let utf8 = if self.is_utf8_compatible_encoding() { self } else { self.conv_enc(handle.utf8_encoding())? }; unsafe { str::from_utf8(utf8.as_slice()) .map_err(|e| Error::new(handle.exception_encoding_error(), format!("{}", e)))? .parse() .map_err(|e| { Error::new( handle.exception_type_error(), format!("could not convert string to char, {}", e), ) }) } } /// Returns a quoted version of the `self`. /// /// This can be thought of as the opposite of `eval`. A string returned /// from `dump` can be safely passed to `eval` and will result in a string /// with the exact same contents as the original. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("🦀 café"); /// assert_eq!(s.dump()?.to_string()?, r#""\u{1F980} caf\u00E9""#); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn dump(self) -> Result { protect(|| unsafe { RString::from_rb_value_unchecked(rb_str_dump(self.as_rb_value())) }) } /// Returns whether `self` is a frozen interned string. Interned strings /// are usually string literals with the in files with the /// `# frozen_string_literal: true` 'magic comment'. /// /// Interned strings won't be garbage collected or modified, so should be /// safe to store on the heap or hold a `&str` refrence to. See /// [`as_interned_str`](RString::as_interned_str). /// /// # Examples /// /// ``` /// use magnus::{eval, Error, RString, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s: RString = eval!( /// ruby, /// r#" /// ## frozen_string_literal: true /// /// "example" /// "# /// )?; /// assert!(s.is_interned()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{eval, Error, RString, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s: RString = eval!(ruby, r#""example""#)?; /// assert!(!s.is_interned()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn is_interned(self) -> bool { unsafe { self.r_basic_unchecked().as_ref().flags & ruby_rstring_flags::RSTRING_FSTR as VALUE != 0 } } /// Returns `Some(FString)` if self is interned, `None` otherwise. /// /// Interned strings won't be garbage collected or modified, so should be /// safe to store on the heap or hold a `&str` refrence to. The `FString` /// type returned by this function provides a way to encode this property /// into the type system, and provides safe methods to access the string /// as a `&str` or slice. /// /// # Examples /// /// ``` /// use magnus::{eval, Error, RString, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s: RString = eval!( /// ruby, /// r#" /// ## frozen_string_literal: true /// /// "example" /// "# /// )?; /// assert!(s.as_interned_str().is_some()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{eval, Error, RString, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s: RString = eval!(ruby, r#""example""#)?; /// assert!(s.as_interned_str().is_none()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn as_interned_str(self) -> Option { self.is_interned().then(|| FString(self)) } /// Interns self and returns a [`FString`]. Be aware that once interned a /// string will never be garbage collected. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let fstring = ruby.str_new("example").to_interned_str(); /// assert_eq!(fstring.as_str()?, "example"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[cfg(any(ruby_gte_3_0, docsrs))] #[cfg_attr(docsrs, doc(cfg(ruby_gte_3_0)))] pub fn to_interned_str(self) -> FString { unsafe { FString(RString::from_rb_value_unchecked(rb_str_to_interned_str( self.as_rb_value(), ))) } } /// Mutate `self`, adding `other` to the end. Errors if `self` and /// `other`'s encodings are not compatible. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = ruby.str_new("foo"); /// let b = ruby.str_new("bar"); /// a.buf_append(b)?; /// assert_eq!(a.to_string()?, "foobar"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn buf_append(self, other: Self) -> Result<(), Error> { protect(|| unsafe { Value::new(rb_str_buf_append(self.as_rb_value(), other.as_rb_value())) })?; Ok(()) } /// Mutate `self`, adding `buf` to the end. /// /// Note: This ignore's `self`'s encoding, and may result in `self` /// containing invalid bytes for its encoding. It's assumed this will more /// often be used with ASCII-8BIT (aka BINARY) encoded strings. See /// [`buf_new`](RString::buf_new) and [`from_slice`](RString::from_slice). /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let buf = ruby.str_buf_new(4096); /// buf.cat(&[102, 111, 111]); /// buf.cat("bar"); /// assert_eq!(buf.to_string()?, "foobar"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn cat>(self, buf: T) { let buf = buf.as_ref(); let len = buf.len(); let ptr = buf.as_ptr(); unsafe { rb_str_cat(self.as_rb_value(), ptr as *const c_char, len as c_long); } } /// Replace the contents and encoding of `self` with those of `other`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = ruby.str_new("foo"); /// let b = ruby.str_new("bar"); /// a.replace(b)?; /// assert_eq!(a.to_string()?, "bar"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn replace(self, other: Self) -> Result<(), Error> { protect(|| { unsafe { rb_str_replace(self.as_rb_value(), other.as_rb_value()) }; Ruby::get_with(self).qnil() })?; Ok(()) } /// Modify `self` to share the same backing data as `other`. /// /// Both string objects will point at the same underlying data until one is /// modified, and only then will the data be duplicated. /// /// See also [`replace`](RString::replace) and /// [`new_shared`](RString::new_shared). /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = ruby.str_new("foo"); /// let b = ruby.str_new("bar"); /// a.shared_replace(b)?; /// assert_eq!(a.to_string()?, "bar"); /// // mutating one doesn't mutate both /// b.cat("foo"); /// assert_eq!(a.to_string()?, "bar"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn shared_replace(self, other: Self) -> Result<(), Error> { protect(|| { unsafe { rb_str_shared_replace(self.as_rb_value(), other.as_rb_value()) }; Ruby::get_with(self).qnil() })?; Ok(()) } /// Replace a portion of `self` with `other`. /// /// `beg` is the offset of the portion of `self` to replace. Negative /// values offset from the end of the string. /// `len` is the length of the portion of `self` to replace. It does not /// need to match the length of `other`, `self` will be expanded or /// contracted as needed to accommodate `other`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("foo"); /// s.update(-1, 1, ruby.str_new("x"))?; /// assert_eq!(s.to_string()?, "fox"); /// /// let s = ruby.str_new("splat"); /// s.update(0, 3, ruby.str_new("b"))?; /// assert_eq!(s.to_string()?, "bat"); /// /// let s = ruby.str_new("corncob"); /// s.update(1, 5, ruby.str_new("ra"))?; /// assert_eq!(s.to_string()?, "crab"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn update(self, beg: isize, len: usize, other: Self) -> Result<(), Error> { protect(|| { unsafe { rb_str_update( self.as_rb_value(), beg as c_long, len as c_long, other.as_rb_value(), ) }; Ruby::get_with(self).qnil() })?; Ok(()) } /// Create a new string by appending `other` to `self`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = ruby.str_new("foo"); /// let b = ruby.str_new("bar"); /// assert_eq!(a.plus(b)?.to_string()?, "foobar"); /// assert_eq!(a.to_string()?, "foo"); /// assert_eq!(b.to_string()?, "bar"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn plus(self, other: Self) -> Result { protect(|| unsafe { Self::from_rb_value_unchecked(rb_str_plus(self.as_rb_value(), other.as_rb_value())) }) } /// Create a new string by repeating `self` `num` times. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert_eq!(ruby.str_new("foo").times(3).to_string()?, "foofoofoo"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn times(self, num: usize) -> Self { let num = Ruby::get_with(self).into_value(num); unsafe { Self::from_rb_value_unchecked(rb_str_times(self.as_rb_value(), num.as_rb_value())) } } /// Shrink `self` by `len` bytes. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("foobar"); /// s.drop_bytes(3)?; /// assert_eq!(s.to_string()?, "bar"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn drop_bytes(self, len: usize) -> Result<(), Error> { protect(|| { unsafe { rb_str_drop_bytes(self.as_rb_value(), len as c_long) }; Ruby::get_with(self).qnil() })?; Ok(()) } /// Returns the number of bytes in `self`. /// /// See also [`length`](RString::length). /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("🦀 Hello, Ferris"); /// assert_eq!(s.len(), 18); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn len(self) -> usize { debug_assert_value!(self); unsafe { RSTRING_LEN(self.as_rb_value()) as usize } } /// Returns the number of characters in `self`. /// /// See also [`len`](RString::len). /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("🦀 Hello, Ferris"); /// assert_eq!(s.length(), 15); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn length(self) -> usize { unsafe { rb_str_strlen(self.as_rb_value()) as usize } } /// Returns the capacity of `self`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_with_capacity(9); /// s.cat("foo"); /// assert_eq!(3, s.len()); /// assert!(s.capacity() >= 9); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn capacity(self) -> usize { unsafe { rb_str_capacity(self.as_rb_value()) as usize } } /// Return whether self contains any characters or not. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new(""); /// assert!(s.is_empty()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn is_empty(self) -> bool { self.len() == 0 } /// Compares `self` with `other` to establish an ordering. /// /// # Examples /// /// ``` /// use std::cmp::Ordering; /// /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = ruby.str_new("a"); /// let b = ruby.str_new("b"); /// assert_eq!(Ordering::Less, a.cmp(b)); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// Note that `std::cmp::Ordering` can be cast to `i{8,16,32,64,size}` to /// get the Ruby standard `-1`/`0`/`+1` for comparison results. /// /// ``` /// assert_eq!(std::cmp::Ordering::Less as i64, -1); /// assert_eq!(std::cmp::Ordering::Equal as i64, 0); /// assert_eq!(std::cmp::Ordering::Greater as i64, 1); /// ``` #[allow(clippy::should_implement_trait)] pub fn cmp(self, other: Self) -> Ordering { unsafe { rb_str_cmp(self.as_rb_value(), other.as_rb_value()) }.cmp(&0) } /// Returns whether there is a total order of strings in the encodings of /// `self` and `other`. /// /// If this function returns `true` for `self` and `other` then the /// ordering returned from [`cmp`](RString::cmp) for those strings is /// 'correct'. If `false`, while stable, the ordering may not follow /// established rules. pub fn comparable(self, other: Self) -> bool { unsafe { rb_str_comparable(self.as_rb_value(), other.as_rb_value()) != 0 } } /// Shorten `self` to `len`, adding "...". /// /// If `self` is shorter than `len` the returned value will be `self`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("foobarbaz"); /// assert_eq!(s.ellipsize(6).to_string()?, "foo..."); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn ellipsize(self, len: usize) -> Self { unsafe { RString::from_rb_value_unchecked(rb_str_ellipsize(self.as_rb_value(), len as c_long)) } } /// Split `self` around the given delimiter. /// /// If `delim` is an empty string then `self` is split into characters. /// If `delim` is solely whitespace then `self` is split around whitespace, /// with leading, trailing, and runs of contiguous whitespace ignored. /// Otherwise, `self` is split around `delim`. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new(" foo bar baz "); /// assert_eq!( /// Vec::::try_convert(s.split("").as_value())?, /// vec![" ", "f", "o", "o", " ", " ", "b", "a", "r", " ", " ", "b", "a", "z", " "] /// ); /// assert_eq!( /// Vec::::try_convert(s.split(" ").as_value())?, /// vec!["foo", "bar", "baz"] /// ); /// assert_eq!( /// Vec::::try_convert(s.split(" bar ").as_value())?, /// vec![" foo ", " baz "] /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn split(self, delim: &str) -> RArray { let delim = CString::new(delim).unwrap(); unsafe { RArray::from_rb_value_unchecked(rb_str_split(self.as_rb_value(), delim.as_ptr())) } } } impl fmt::Display for RString { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for RString { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl EncodingCapable for RString {} impl io::Write for RString { fn write(&mut self, buf: &[u8]) -> io::Result { let len = buf.len(); self.cat(buf); Ok(len) } fn flush(&mut self) -> io::Result<()> { Ok(()) } } /// Conversions from Rust types into [`RString`]. pub trait IntoRString: Sized { /// Convert `self` into [`RString`]. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`IntoRString::into_r_string_with`] for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `IntoRString::into_r_string_with` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] fn into_r_string(self) -> RString { self.into_r_string_with(&get_ruby!()) } /// Convert `self` into [`RString`]. /// /// # Safety /// /// This method should only be called from a Ruby thread. unsafe fn into_r_string_unchecked(self) -> RString { self.into_r_string_with(&Ruby::get_unchecked()) } /// Convert `self` into [`RString`]. fn into_r_string_with(self, handle: &Ruby) -> RString; } impl IntoRString for RString { fn into_r_string_with(self, _: &Ruby) -> RString { self } } impl IntoRString for &str { fn into_r_string_with(self, handle: &Ruby) -> RString { handle.str_new(self) } } impl IntoRString for String { fn into_r_string_with(self, handle: &Ruby) -> RString { handle.str_new(&self) } } #[cfg(unix)] impl IntoRString for &Path { fn into_r_string_with(self, handle: &Ruby) -> RString { use std::os::unix::ffi::OsStrExt; handle.str_from_slice(self.as_os_str().as_bytes()) } } #[cfg(windows)] impl IntoRString for &Path { fn into_r_string_with(self, handle: &Ruby) -> RString { use std::os::windows::ffi::OsStrExt; if let Some(utf16) = handle.find_encoding("UTF-16LE") { let bytes: Vec = self .as_os_str() .encode_wide() .flat_map(|c| c.to_le_bytes()) .collect(); handle.enc_str_new(bytes, utf16) } else { handle.str_new(self.to_string_lossy().as_ref()) } } } #[cfg(not(any(unix, windows)))] impl IntoRString for &Path { fn into_r_string_with(self, handle: &Ruby) -> RString { handle.str_new(self.to_string_lossy().as_ref()) } } impl IntoRString for PathBuf { fn into_r_string_with(self, handle: &Ruby) -> RString { self.as_path().into_r_string_with(handle) } } impl IntoValue for RString { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl IntoValue for &str { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle.str_new(self).into_value_with(handle) } } unsafe impl IntoValueFromNative for &str {} #[cfg(feature = "bytes")] impl IntoValue for bytes::Bytes { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle.str_from_slice(self.as_ref()).into_value_with(handle) } } impl IntoValue for String { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle.str_new(self.as_str()).into_value_with(handle) } } unsafe impl IntoValueFromNative for String {} impl IntoValue for char { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle.str_from_char(self).into_value_with(handle) } } unsafe impl IntoValueFromNative for char {} impl IntoValue for &Path { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { self.into_r_string_with(handle).into_value_with(handle) } } unsafe impl IntoValueFromNative for &Path {} impl IntoValue for PathBuf { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { self.as_path() .into_r_string_with(handle) .into_value_with(handle) } } unsafe impl IntoValueFromNative for PathBuf {} impl Object for RString {} unsafe impl private::ReprValue for RString {} impl ReprValue for RString {} impl TryConvert for RString { fn try_convert(val: Value) -> Result { match Self::from_value(val) { Some(i) => Ok(i), None => protect(|| { debug_assert_value!(val); unsafe { Self::from_rb_value_unchecked(rb_str_to_str(val.as_rb_value())) } }), } } } /// FString contains an RString known to be interned. /// /// Interned strings won't be garbage collected or modified, so should be /// safe to store on the heap or hold a `&str` refrence to. `FString` provides /// a way to encode this property into the type system, and provides safe /// methods to access the string as a `&str` or slice. #[derive(Clone, Copy)] #[repr(transparent)] pub struct FString(RString); impl FString { /// Returns the interned string as a [`RString`]. pub fn as_r_string(self) -> RString { self.0 } /// Returns the interned string as a slice of bytes. /// /// # Examples /// /// ``` /// use magnus::{eval, Error, RString, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s: RString = eval!( /// ruby, /// r#" /// ## frozen_string_literal: true /// /// "example" /// "# /// )?; /// let fstring = s.as_interned_str().unwrap(); /// assert_eq!(fstring.as_slice(), &[101, 120, 97, 109, 112, 108, 101]); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn as_slice(self) -> &'static [u8] { unsafe { self.as_r_string().as_slice_unconstrained() } } /// Returns the interned string as a `&str` or `None` string contains /// invliad UTF-8. /// /// # Examples /// /// ``` /// use magnus::{eval, Error, RString, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s: RString = eval!( /// ruby, /// r#" /// ## frozen_string_literal: true /// /// "example" /// "# /// )?; /// let fstring = s.as_interned_str().unwrap(); /// assert_eq!(fstring.test_as_str().unwrap(), "example"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn test_as_str(self) -> Option<&'static str> { unsafe { self.as_r_string().test_as_str_unconstrained() } } /// Returns the interned string as a `&str`. Errors if the string contains /// invliad UTF-8. /// /// # Examples /// /// ``` /// use magnus::{eval, Error, RString, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s: RString = eval!( /// ruby, /// r#" /// ## frozen_string_literal: true /// /// "example" /// "# /// )?; /// let fstring = s.as_interned_str().unwrap(); /// assert_eq!(fstring.as_str()?, "example"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn as_str(self) -> Result<&'static str, Error> { unsafe { self.as_r_string().as_str_unconstrained() } } /// Returns interned string as a Rust string, ignoring the Ruby encoding /// and dropping any non-UTF-8 characters. If the string is valid UTF-8 /// this will return a `&str` reference. /// /// # Examples /// /// ``` /// use magnus::{eval, Error, RString, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s: RString = eval!( /// ruby, /// r#" /// ## frozen_string_literal: true /// /// "example" /// "# /// )?; /// let fstring = s.as_interned_str().unwrap(); /// assert_eq!(fstring.to_string_lossy(), "example"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn to_string_lossy(self) -> Cow<'static, str> { String::from_utf8_lossy(self.as_slice()) } } impl fmt::Display for FString { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.as_r_string().to_s_infallible() }) } } impl fmt::Debug for FString { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_r_string().inspect()) } } impl IntoValue for FString { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { self.as_r_string().into_value_with(handle) } } unsafe impl private::ReprValue for FString {} impl ReprValue for FString {} /// An iterator over a Ruby string's codepoints. pub struct Codepoints<'a> { slice: &'a [u8], encoding: RbEncoding, } impl<'a> Iterator for Codepoints<'a> { type Item = Result; fn next(&mut self) -> Option { if self.slice.is_empty() { return None; } match self.encoding.codepoint_len(self.slice) { Ok((codepoint, len)) => { self.slice = &self.slice[len..]; Some(Ok(codepoint)) } Err(e) => { self.slice = &self.slice[self.slice.len()..]; Some(Err(e)) } } } } /// An iterator over a Ruby string's chars as slices of bytes. pub struct CharBytes<'a> { slice: &'a [u8], encoding: RbEncoding, } impl<'a> Iterator for CharBytes<'a> { type Item = &'a [u8]; fn next(&mut self) -> Option { if self.slice.is_empty() { return None; } let len = self.encoding.mbclen(self.slice); let bytes = &self.slice[..len]; self.slice = &self.slice[len..]; Some(bytes) } } /// Create a [`RString`] from a Rust str literal. /// /// # Panics /// /// Panics if called from a non-Ruby thread. /// /// # Examples /// /// ``` /// use magnus::{r_string, rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = r_string!("Hello, world!"); /// rb_assert!(ruby, r#"s == "Hello, world!""#, s); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[macro_export] macro_rules! r_string { ($lit:expr) => {{ $crate::r_string!($crate::Ruby::get().unwrap(), $lit) }}; ($ruby:expr, $lit:expr) => {{ let s = concat!($lit, "\0"); let len = s.len() - 1; unsafe { $ruby.str_new_lit(s.as_ptr() as *const _, len as _) } }}; } magnus-0.7.1/src/r_struct.rs000064400000000000000000000432771046102023000141420ustar 00000000000000//! Types and functions for working with Ruby's Struct class. //! //! See also [`Ruby`](Ruby#struct) for more Struct related methods. use std::{ borrow::Cow, ffi::CString, fmt, os::raw::c_char, ptr::{null, NonNull}, slice, }; #[cfg(ruby_gte_3_3)] use rb_sys::rb_data_define; use rb_sys::{ rb_struct_aref, rb_struct_aset, rb_struct_define, rb_struct_getmember, rb_struct_members, rb_struct_size, ruby_value_type, VALUE, }; use seq_macro::seq; use crate::{ class::RClass, error::{protect, Error}, into_value::IntoValue, object::Object, r_array::RArray, symbol::Symbol, try_convert::TryConvert, value::{self, private::ReprValue as _, IntoId, NonZeroValue, ReprValue, Value}, Ruby, }; // Ruby provides some inline functions to get a pointer to the struct's // contents, but we have to reimplement those for Rust. The for that we need // the definition of RStruct, but that isn't public, so we have to duplicate it // here. mod sys { #[cfg(ruby_lt_3_0)] use rb_sys::ruby_fl_type::RUBY_FL_USHIFT; #[cfg(ruby_gte_3_0)] use rb_sys::ruby_fl_ushift::RUBY_FL_USHIFT; use rb_sys::{ruby_fl_type, RBasic, VALUE}; pub const EMBED_LEN_MAX: u32 = rb_sys::ruby_rvalue_flags::RVALUE_EMBED_LEN_MAX as u32; pub const EMBED_LEN_MASK: u32 = ruby_fl_type::RUBY_FL_USER2 as u32 | ruby_fl_type::RUBY_FL_USER1 as u32; pub const EMBED_LEN_SHIFT: u32 = RUBY_FL_USHIFT as u32 + 1; #[repr(C)] #[derive(Copy, Clone)] pub struct RStruct { pub basic: RBasic, pub as_: As, } #[repr(C)] #[derive(Copy, Clone)] pub union As { pub heap: Heap, pub ary: [VALUE; EMBED_LEN_MAX as usize], } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct Heap { pub len: std::os::raw::c_long, pub ptr: *const VALUE, } } /// A Value pointer to a RStruct struct, Ruby’s internal representation of /// 'Structs'. /// /// See the [`ReprValue`] and [`Object`] traits for additional methods /// available on this type. #[derive(Clone, Copy)] #[repr(transparent)] pub struct RStruct(NonZeroValue); impl RStruct { /// Return `Some(RStruct)` if `val` is a `RStruct`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{Error, RStruct, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let struct_class = ruby.define_struct(None, ("foo", "bar"))?; /// ruby.define_global_const("Example", struct_class)?; /// /// assert!(RStruct::from_value(ruby.eval(r#"Example.new(1, 2)"#)?).is_some()); /// assert!(RStruct::from_value(ruby.eval(r#"Object.new"#)?).is_none()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { (val.rb_type() == ruby_value_type::RUBY_T_STRUCT) .then(|| Self(NonZeroValue::new_unchecked(val))) } } pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } fn as_internal(self) -> NonNull { // safe as inner value is NonZero unsafe { NonNull::new_unchecked(self.0.get().as_rb_value() as *mut _) } } /// Return the members of the struct as a slice of [`Value`]s. The order /// will be the order the of the member names when the struct class was /// defined. /// /// # Safety /// /// Ruby may modify or free the memory backing the returned slice, the /// caller must ensure this does not happen. unsafe fn as_slice(&self) -> &[Value] { self.as_slice_unconstrained() } unsafe fn as_slice_unconstrained<'a>(self) -> &'a [Value] { debug_assert_value!(self); let r_basic = self.r_basic_unchecked(); let flags = r_basic.as_ref().flags; if (flags & sys::EMBED_LEN_MASK as VALUE) != 0 { let len = (flags & sys::EMBED_LEN_MASK as VALUE) >> sys::EMBED_LEN_SHIFT as VALUE; slice::from_raw_parts( &self.as_internal().as_ref().as_.ary as *const VALUE as *const Value, len as usize, ) } else { let h = self.as_internal().as_ref().as_.heap; slice::from_raw_parts(h.ptr as *const Value, h.len as usize) } } /// Return the value for the member at `index`, where members are ordered /// as per the member names when the struct class was defined. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, RStruct, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let struct_class = ruby.define_struct(None, ("foo", "bar"))?; /// let instance = RStruct::from_value(struct_class.new_instance((1, 2))?).unwrap(); /// assert_eq!(instance.get::(0)?, 1); /// assert_eq!(instance.get::(1)?, 2); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn get(self, index: usize) -> Result where T: TryConvert, { unsafe { let slice = self.as_slice(); slice .get(index) .copied() .ok_or_else(|| { Error::new( Ruby::get_with(self).exception_index_error(), format!( "offset {} too large for struct(size:{})", index, slice.len() ), ) }) .and_then(TryConvert::try_convert) } } /// Return the value for the member at `index`. /// /// `index` may be an integer, string, or [`Symbol`]. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, RStruct, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let struct_class = ruby.define_struct(None, ("foo", "bar", "baz"))?; /// let instance = RStruct::from_value(struct_class.new_instance((1, 2, 3))?).unwrap(); /// assert_eq!(instance.aref::<_, i64>(0)?, 1); /// assert_eq!(instance.aref::<_, i64>("bar")?, 2); /// assert_eq!(instance.aref::<_, i64>(ruby.to_symbol("baz"))?, 3); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn aref(self, index: T) -> Result where T: IntoValue, U: TryConvert, { let index = Ruby::get_with(self).into_value(index); protect(|| unsafe { Value::new(rb_struct_aref(self.as_rb_value(), index.as_rb_value())) }) .and_then(TryConvert::try_convert) } /// Set the value for the member at `index`. /// /// `index` may be an integer, string, or [`Symbol`]. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, rb_assert, Error, RStruct, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let struct_class = ruby.define_struct(None, ("foo", "bar", "baz"))?; /// let instance = RStruct::from_value(struct_class.new_instance((1, 2, 3))?).unwrap(); /// /// instance.aset(0, 4)?; /// rb_assert!("instance.foo == 4", instance); /// /// instance.aset("bar", 5)?; /// rb_assert!("instance.bar == 5", instance); /// /// instance.aset(ruby.to_symbol("baz"), 6)?; /// rb_assert!("instance.baz == 6", instance); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn aset(self, index: T, val: U) -> Result<(), Error> where T: IntoValue, U: IntoValue, { let handle = Ruby::get_with(self); let index = handle.into_value(index); let val = handle.into_value(val); unsafe { protect(|| { Value::new(rb_struct_aset( self.as_rb_value(), index.as_rb_value(), val.as_rb_value(), )) })?; } Ok(()) } /// Returns the count of members this struct has. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, RStruct, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let struct_class = ruby.define_struct(None, ("foo", "bar", "baz"))?; /// let instance = RStruct::from_value(struct_class.new_instance(())?).unwrap(); /// /// assert_eq!(instance.size(), 3); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn size(self) -> usize { unsafe { usize::try_convert(Value::new(rb_struct_size(self.as_rb_value()))).unwrap() } } /// Returns the member names for this struct. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, RStruct, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let struct_class = ruby.define_struct(None, ("foo", "bar", "baz"))?; /// let instance = RStruct::from_value(struct_class.new_instance(())?).unwrap(); /// /// assert_eq!(instance.members()?, &["foo", "bar", "baz"]); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn members(self) -> Result>, Error> { unsafe { let array = RArray::from_rb_value_unchecked(rb_struct_members(self.as_rb_value())); array .as_slice() .iter() .map(|v| Symbol::from_value(*v).unwrap().name()) .collect() } } /// Return the value for the member named `id`. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, RStruct, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let struct_class = ruby.define_struct(None, ("foo", "bar"))?; /// let instance = RStruct::from_value(struct_class.new_instance((1, 2))?).unwrap(); /// assert_eq!(instance.getmember::<_, i64>("foo")?, 1); /// assert_eq!(instance.getmember::<_, i64>("bar")?, 2); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn getmember(self, id: T) -> Result where T: IntoId, U: TryConvert, { let id = id.into_id_with(&Ruby::get_with(self)); protect(|| unsafe { Value::new(rb_struct_getmember(self.as_rb_value(), id.as_rb_id())) }) .and_then(TryConvert::try_convert) } } impl fmt::Display for RStruct { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for RStruct { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for RStruct { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl Object for RStruct {} unsafe impl value::private::ReprValue for RStruct {} impl ReprValue for RStruct {} impl TryConvert for RStruct { fn try_convert(val: Value) -> Result { Self::from_value(val).ok_or_else(|| { Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into Struct", unsafe { val.classname() },), ) }) } } /// # `Struct` /// /// Functions that can be used to create Ruby `Struct` classes. /// /// See also the [`struct`](self) module. impl Ruby { /// Define a Ruby Struct class. /// /// `members` is a tuple of `&str`, of between lengths 1 to 12 inclusive. /// /// # Examples /// /// When providing `None` for the `name` the struct class's name will be /// taken from the first constant it is assigned to: /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let struct_class = ruby.define_struct(None, ("foo", "bar"))?; /// ruby.define_global_const("Example", struct_class)?; /// /// assert_eq!(unsafe { struct_class.name().to_owned() }, "Example"); /// /// let instance = struct_class.new_instance((1, 2))?; /// assert_eq!(instance.inspect(), "#"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// When providing `Some("Name")` for the `name` the struct will be defined /// under `Struct`: /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let struct_class = ruby.define_struct(Some("Example"), ("foo", "bar"))?; /// /// assert_eq!(unsafe { struct_class.name().to_owned() }, "Struct::Example"); /// /// let instance = struct_class.new_instance((1, 2))?; /// assert_eq!(instance.inspect(), "#"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn define_struct(&self, name: Option<&str>, members: T) -> Result where T: StructMembers, { members.define(name) } /// Define a Ruby Data class. /// /// If provided, `super_class` must be a subclass of Ruby's `Data` class /// (or `Data` itself). /// /// `members` is a tuple of `&str`, of between lengths 1 to 12 inclusive. /// /// ``` /// use magnus::{kwargs, prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let data_class = ruby.define_data(None, ("foo", "bar"))?; /// ruby.define_global_const("Example", data_class)?; /// /// assert_eq!(unsafe { data_class.name().to_owned() }, "Example"); /// /// let instance = data_class.new_instance((kwargs!("foo" => 1, "bar" => 2),))?; /// assert_eq!(instance.inspect(), "#"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[cfg(any(ruby_gte_3_3, docsrs))] #[cfg_attr(docsrs, doc(cfg(ruby_gte_3_3)))] pub fn define_data(&self, super_class: Option, members: T) -> Result where T: StructMembers, { members.define_data(super_class) } } /// Define a Ruby Struct class. /// /// `members` is a tuple of `&str`, of between lengths 1 to 12 inclusive. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::define_struct`] for /// the non-panicking version. /// /// # Examples /// /// When providing `None` for the `name` the struct class's name will be taken /// from the first constant it is assigned to: /// /// ``` /// # #![allow(deprecated)] /// use magnus::{define_global_const, prelude::*, r_struct::define_struct}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let struct_class = define_struct(None, ("foo", "bar")).unwrap(); /// define_global_const("Example", struct_class).unwrap(); /// /// assert_eq!(unsafe { struct_class.name().to_owned() }, "Example"); /// /// let instance = struct_class.new_instance((1, 2)).unwrap(); /// assert_eq!(instance.inspect(), "#") /// ``` /// /// When providing `Some("Name")` for the `name` the struct will be defined /// under `Struct`: /// /// ``` /// # #![allow(deprecated)] /// use magnus::{prelude::*, r_struct::define_struct}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let struct_class = define_struct(Some("Example"), ("foo", "bar")).unwrap(); /// /// assert_eq!(unsafe { struct_class.name().to_owned() }, "Struct::Example"); /// /// let instance = struct_class.new_instance((1, 2)).unwrap(); /// assert_eq!(instance.inspect(), "#") /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::define_struct` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn define_struct(name: Option<&str>, members: T) -> Result where T: StructMembers, { get_ruby!().define_struct(name, members) } mod private { use super::*; pub trait StructMembers { fn define(self, name: Option<&str>) -> Result; #[cfg(ruby_gte_3_3)] fn define_data(self, super_class: Option) -> Result; } } use private::StructMembers; macro_rules! impl_struct_members { ($n:literal) => { seq!(N in 0..$n { impl StructMembers for (#(&str,)*) { fn define(self, name: Option<&str>) -> Result { let name = name.map(|n| CString::new(n).unwrap()); #(let arg~N = CString::new(self.N).unwrap();)* protect(|| unsafe { RClass::from_rb_value_unchecked(rb_struct_define( name.as_ref().map(|n| n.as_ptr()).unwrap_or_else(null), #(arg~N.as_ptr(),)* null::(), )) }) } #[cfg(ruby_gte_3_3)] fn define_data(self, super_class: Option) -> Result { #(let arg~N = CString::new(self.N).unwrap();)* protect(|| unsafe { RClass::from_rb_value_unchecked(rb_data_define( super_class.map(|s| s.as_rb_value()).unwrap_or(0), #(arg~N.as_ptr(),)* null::(), )) }) } } }); } } seq!(N in 1..=12 { impl_struct_members!(N); }); magnus-0.7.1/src/r_typed_data.rs000064400000000000000000000317521046102023000147270ustar 00000000000000use std::{fmt, ptr::NonNull}; use rb_sys::{self, rb_check_typeddata, rb_data_typed_object_wrap, ruby_value_type, VALUE}; use crate::{ class::RClass, error::{protect, Error}, into_value::IntoValue, module::Module, object::Object, typed_data::TypedData, value::{ private::{self, ReprValue as _}, NonZeroValue, ReprValue, Value, }, Ruby, }; /// # `RTypedData` /// /// Functions to wrap Rust data in a Ruby object. /// /// See also [`typed_data::Obj`](Ruby#typed_dataobj) and the [`RTypedData`] /// type. impl Ruby { /// Wrap the Rust type `T` in a Ruby object. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// #[magnus::wrap(class = "Point")] /// struct Point { /// x: isize, /// y: isize, /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let point_class = ruby.define_class("Point", ruby.class_object())?; /// /// let value = ruby.wrap(Point { x: 4, y: 2 }); /// assert!(value.is_kind_of(point_class)); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap(); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` #[inline] pub fn wrap(&self, data: T) -> RTypedData where T: TypedData, { let class = T::class_for(self, &data); self.wrap_as(data, class) } /// Wrap the Rust type `T` in a Ruby object that is an instance of the /// given `class`. /// /// See also [`TypedData::class_for`]. /// /// # Panics /// /// Panics if `class` is not a subclass of `::class()`. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// #[magnus::wrap(class = "Point")] /// struct Point { /// x: isize, /// y: isize, /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let point_class = ruby.define_class("Point", ruby.class_object())?; /// let point_sub_class = ruby.define_class("SubPoint", point_class)?; /// /// let value = ruby.wrap_as(Point { x: 4, y: 2 }, point_sub_class); /// assert!(value.is_kind_of(point_sub_class)); /// assert!(value.is_kind_of(point_class)); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap(); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` /// /// Allowing a wrapped type to be subclassed from Ruby: /// /// (note, in this example `Point` does not have and does not call /// the `initialize` method, subclasses would need to override the class /// `new` method rather than `initialize`) /// /// ``` /// use magnus::{function, method, prelude::*, Error, RClass, RTypedData, Ruby, Value}; /// /// #[magnus::wrap(class = "Point")] /// struct Point { /// x: isize, /// y: isize, /// } /// /// impl Point { /// fn new(ruby: &Ruby, class: RClass, x: isize, y: isize) -> RTypedData { /// ruby.wrap_as(Self { x, y }, class) /// } /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let point_class = ruby.define_class("Point", ruby.class_object())?; /// point_class.define_singleton_method("new", method!(Point::new, 2))?; /// point_class /// .define_singleton_method("inherited", function!(RClass::undef_default_alloc_func, 1))?; /// /// let value: Value = ruby.eval( /// r#" /// class SubPoint < Point /// end /// SubPoint.new(4, 2) /// "#, /// )?; /// /// assert!(value.is_kind_of(ruby.class_object().const_get::<_, RClass>("SubPoint")?)); /// assert!(value.is_kind_of(point_class)); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap(); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` pub fn wrap_as(&self, data: T, class: RClass) -> RTypedData where T: TypedData, { debug_assert!( class.is_inherited(T::class(self)), "{} is not a subclass of {}", class, T::class(self) ); let boxed = Box::new(data); unsafe { let value_ptr = rb_data_typed_object_wrap( class.as_rb_value(), Box::into_raw(boxed) as *mut _, T::data_type().as_rb_data_type() as *const _, ); RTypedData(NonZeroValue::new_unchecked(Value::new(value_ptr))) } } } /// A Value pointer to a RTypedData struct, Ruby’s internal representation of /// objects that wrap foreign types. /// /// See also [`typed_data::Obj`](crate::typed_data::Obj). /// /// See the [`ReprValue`] and [`Object`] traits for additional methods /// available on this type. See [`Ruby`](Ruby#rtypeddata) for methods to create /// an `RTypedData`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct RTypedData(NonZeroValue); impl RTypedData { /// Return `Some(RTypedData)` if `val` is a `RTypedData`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{function, prelude::*, Error, RTypedData, Ruby}; /// /// #[magnus::wrap(class = "Point")] /// struct Point { /// x: isize, /// y: isize, /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let point_class = ruby.define_class("Point", ruby.class_object())?; /// point_class.define_singleton_method("new", function!(|x, y| Point { x, y }, 2))?; /// /// assert!(RTypedData::from_value(ruby.eval(r#"Point.new(1, 2)"#)?).is_some()); /// assert!(RTypedData::from_value(ruby.eval(r#"Object.new"#)?).is_none()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap(); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { (val.rb_type() == ruby_value_type::RUBY_T_DATA) .then(|| NonNull::new_unchecked(val.as_rb_value() as *mut rb_sys::RTypedData)) .and_then(|typed_data| { let typed_flag = typed_data.as_ref().typed_flag; (typed_flag != 0 && typed_flag <= 3) .then(|| Self(NonZeroValue::new_unchecked(val))) }) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } /// Wrap the Rust type `T` in a Ruby object. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::wrap`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{class, define_class, prelude::*, RTypedData}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// #[magnus::wrap(class = "Point")] /// struct Point { /// x: isize, /// y: isize, /// } /// /// let point_class = define_class("Point", class::object()).unwrap(); /// /// let value = RTypedData::wrap(Point { x: 4, y: 2 }); /// assert!(value.is_kind_of(point_class)); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::wrap` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn wrap(data: T) -> Self where T: TypedData, { get_ruby!().wrap(data) } /// Wrap the Rust type `T` in a Ruby object that is an instance of the /// given `class`. /// /// See also [`TypedData::class_for`]. /// /// # Panics /// /// Panics if `class` is not a subclass of `::class()`, or /// if called from a non-Ruby thread. See [`Ruby::wrap_as`] for a version /// that can not be called from a non-Ruby thread, so will not panic for /// that reason. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{class, define_class, prelude::*, RTypedData}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// #[magnus::wrap(class = "Point")] /// struct Point { /// x: isize, /// y: isize, /// } /// /// let point_class = define_class("Point", class::object()).unwrap(); /// let point_sub_class = define_class("SubPoint", point_class).unwrap(); /// /// let value = RTypedData::wrap_as(Point { x: 4, y: 2 }, point_sub_class); /// assert!(value.is_kind_of(point_sub_class)); /// assert!(value.is_kind_of(point_class)); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` /// /// Allowing a wrapped type to be subclassed from Ruby: /// /// (note, in this example `Point` does not have and does not call /// the `initialize` method, subclasses would need to override the class /// `new` method rather than `initialize`) /// /// ``` /// # #![allow(deprecated)] /// use magnus::{ /// class, define_class, eval, function, method, prelude::*, RClass, RTypedData, Value, /// }; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// #[magnus::wrap(class = "Point")] /// struct Point { /// x: isize, /// y: isize, /// } /// /// impl Point { /// fn new(class: RClass, x: isize, y: isize) -> RTypedData { /// RTypedData::wrap_as(Self { x, y }, class) /// } /// } /// /// let point_class = define_class("Point", class::object()).unwrap(); /// point_class /// .define_singleton_method("new", method!(Point::new, 2)) /// .unwrap(); /// point_class /// .define_singleton_method("inherited", function!(RClass::undef_default_alloc_func, 1)) /// .unwrap(); /// /// let value: Value = eval( /// r#" /// class SubPoint < Point /// end /// SubPoint.new(4, 2) /// "#, /// ) /// .unwrap(); /// /// assert!(value.is_kind_of(class::object().const_get::<_, RClass>("SubPoint").unwrap())); /// assert!(value.is_kind_of(point_class)); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::wrap_as` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn wrap_as(data: T, class: RClass) -> Self where T: TypedData, { get_ruby!().wrap_as(data, class) } /// Get a reference to the Rust type wrapped in the Ruby object `self`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// #[magnus::wrap(class = "Point")] /// #[derive(Debug, PartialEq, Eq)] /// struct Point { /// x: isize, /// y: isize, /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_class("Point", ruby.class_object())?; /// let value = ruby.wrap(Point { x: 4, y: 2 }); /// /// assert_eq!(value.get::()?, &Point { x: 4, y: 2 }); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn get(&self) -> Result<&T, Error> where T: TypedData, { unsafe { self.get_unconstrained() } } /// Get a reference to the Rust type wrapped in the Ruby object `self`. /// /// # Safety /// /// This method can magic any lifetime needed out of thin air, even /// `'static`. #[inline] pub(crate) unsafe fn get_unconstrained<'a, T>(self) -> Result<&'a T, Error> where T: TypedData, { debug_assert_value!(self); let handle = Ruby::get_with(self); let mut res = None; let _ = protect(|| { res = (rb_check_typeddata( self.as_rb_value(), T::data_type().as_rb_data_type() as *const _, ) as *const T) .as_ref(); handle.qnil() }); res.ok_or_else(|| { Error::new( handle.exception_type_error(), format!( "no implicit conversion of {} into {}", self.classname(), T::class(&handle) ), ) }) } } impl fmt::Display for RTypedData { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for RTypedData { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for RTypedData { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl Object for RTypedData {} unsafe impl private::ReprValue for RTypedData {} impl ReprValue for RTypedData {} magnus-0.7.1/src/range.rs000064400000000000000000000263211046102023000133600ustar 00000000000000//! Types for working with Ruby ranges. use std::{ fmt, ops::{Range as StdRange, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}, os::raw::{c_int, c_long}, }; use rb_sys::{rb_range_beg_len, rb_range_new}; use crate::{ error::{protect, Error}, into_value::{IntoValue, IntoValueFromNative}, object::Object, r_struct::RStruct, try_convert::TryConvert, value::{ private::{self, ReprValue as _}, ReprValue, Value, }, Ruby, }; /// # `Range` /// /// Functions that can be used to create Ruby `Range`s. /// /// See also the [`Range`] type. impl Ruby { /// Create a new `Range`. /// /// Returns `Err` if `beg` and `end` are not comparable. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let range = ruby.range_new(2, 7, false)?; /// rb_assert!(ruby, "range == (2..7)", range); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let range = ruby.range_new(2, 7, true)?; /// rb_assert!(ruby, "range == (2...7)", range); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn range_new(&self, beg: T, end: U, excl: bool) -> Result where T: IntoValue, U: IntoValue, { protect(|| unsafe { Range(RStruct::from_rb_value_unchecked(rb_range_new( self.into_value(beg).as_rb_value(), self.into_value(end).as_rb_value(), excl as c_int, ))) }) } } /// Wrapper type for a Value known to be an instance of Ruby's Range class. /// /// See the [`ReprValue`] and [`Object`] traits for additional methods /// available on this type. See [`Ruby`](Ruby#range) for methods to create a /// `Range`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct Range(RStruct); impl Range { /// Return `Some(Range)` if `val` is an `Range`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::eval; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(magnus::Range::from_value(eval("2..7").unwrap()).is_some()); /// assert!(magnus::Range::from_value(eval("1").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { RStruct::from_value(val) .filter(|_| val.is_kind_of(Ruby::get_with(val).class_range())) .map(Self) } /// Create a new `Range`. /// /// Returns `Err` if `beg` and `end` are not comparable. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::range_new`] for /// the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::rb_assert; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let range = magnus::Range::new(2, 7, false).unwrap(); /// rb_assert!("range == (2..7)", range); /// ``` /// /// ``` /// # #![allow(deprecated)] /// use magnus::rb_assert; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let range = magnus::Range::new(2, 7, true).unwrap(); /// rb_assert!("range == (2...7)", range); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::range_new` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn new(beg: T, end: U, excl: bool) -> Result where T: IntoValue, U: IntoValue, { get_ruby!().range_new(beg, end, excl) } /// Return the value that defines the beginning of the range, converting it /// to a `T`. /// /// Errors if the conversion fails. /// /// # Examples /// /// ``` /// use magnus::eval; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let range: magnus::Range = eval("2..7").unwrap(); /// assert_eq!(range.beg::().unwrap(), 2); /// ``` pub fn beg(self) -> Result where T: TryConvert, { self.0.get(0) } /// Return the value that defines the end of the range, converting it /// to a `T`. /// /// Errors if the conversion fails. /// /// # Examples /// /// ``` /// use magnus::eval; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let range: magnus::Range = eval("2..7").unwrap(); /// assert_eq!(range.end::().unwrap(), 7); /// ``` pub fn end(self) -> Result where T: TryConvert, { self.0.get(1) } /// Returns `true` if the range excludes its end value, `false` if the end /// value is included. /// /// # Examples /// /// ``` /// use magnus::eval; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let range: magnus::Range = eval("2..7").unwrap(); /// assert_eq!(range.excl(), false); /// ``` pub fn excl(self) -> bool { self.0.get::(2).unwrap().to_bool() } /// Given a total `length`, returns a beginning index and length of the /// range within that total length. /// /// Returns `Err` if `self` is a non-numerical range, or the range is out /// of range for `length`. /// /// # Examples /// /// ``` /// use magnus::eval; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let range: magnus::Range = eval("2..").unwrap(); /// assert_eq!(range.beg_len(10).unwrap(), (2, 8)); /// ``` /// /// ``` /// use magnus::eval; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let range: magnus::Range = eval("..7").unwrap(); /// assert_eq!(range.beg_len(10).unwrap(), (0, 8)); /// ``` /// /// ``` /// use magnus::eval; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let range: magnus::Range = eval("-3..-1").unwrap(); /// assert_eq!(range.beg_len(10).unwrap(), (7, 3)); /// ``` pub fn beg_len(self, length: usize) -> Result<(usize, usize), Error> { let mut begp: c_long = 0; let mut lenp: c_long = 0; protect(|| unsafe { Value::new(rb_range_beg_len( self.as_rb_value(), &mut begp as *mut _, &mut lenp as *mut _, length as c_long, 1, )) })?; Ok((begp as usize, lenp as usize)) } /// Given a total `length`, converts the Ruby `Range` to a Rust /// [`std::ops::Range`]. /// /// `length` is required to account for Ruby conventions such as a range /// from `-2..-1` returning the end of a collection. /// /// Returns `Err` if `self` is a non-numerical range, or the range is out /// of range for `length`. /// /// # Examples /// /// ``` /// use magnus::eval; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// // Ruby's .. range is inclusive /// let range: magnus::Range = eval("2..7").unwrap(); /// // Rust's .. range in exclusive /// assert_eq!(range.to_range_with_len(10).unwrap(), 2..8); /// ``` /// /// ``` /// use magnus::eval; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let range: magnus::Range = eval("2..").unwrap(); /// assert_eq!(range.to_range_with_len(10).unwrap(), 2..10); /// ``` /// /// ``` /// use magnus::eval; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let range: magnus::Range = eval("..7").unwrap(); /// assert_eq!(range.to_range_with_len(10).unwrap(), 0..8); /// ``` /// /// ``` /// use magnus::eval; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let range: magnus::Range = eval("-3..-1").unwrap(); /// assert_eq!(range.to_range_with_len(10).unwrap(), 7..10); /// ``` pub fn to_range_with_len(self, length: usize) -> Result, Error> { let (beg, len) = self.beg_len(length)?; Ok(beg..(beg + len)) } } impl fmt::Display for Range { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for Range { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for Range { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { self.0.into_value_with(handle) } } impl IntoValue for StdRange where T: IntoValue, { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle .range_new(self.start, self.end, true) .unwrap() .into_value_with(handle) } } unsafe impl IntoValueFromNative for StdRange where T: IntoValueFromNative {} impl IntoValue for RangeFrom where T: IntoValue, { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle .range_new(self.start, handle.qnil(), false) .unwrap() .into_value_with(handle) } } unsafe impl IntoValueFromNative for RangeFrom where T: IntoValueFromNative {} impl IntoValue for RangeFull { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle .range_new(handle.qnil(), handle.qnil(), false) .unwrap() .into_value_with(handle) } } unsafe impl IntoValueFromNative for RangeFull {} impl IntoValue for RangeInclusive where T: IntoValue, { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { let (start, end) = self.into_inner(); handle .range_new(start, end, false) .unwrap() .into_value_with(handle) } } unsafe impl IntoValueFromNative for RangeInclusive where T: IntoValueFromNative {} impl IntoValue for RangeTo where T: IntoValue, { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle .range_new(handle.qnil(), self.end, true) .unwrap() .into_value_with(handle) } } unsafe impl IntoValueFromNative for RangeTo where T: IntoValueFromNative {} impl IntoValue for RangeToInclusive where T: IntoValue, { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle .range_new(handle.qnil(), self.end, false) .unwrap() .into_value_with(handle) } } unsafe impl IntoValueFromNative for RangeToInclusive where T: IntoValueFromNative {} impl Object for Range {} unsafe impl private::ReprValue for Range {} impl ReprValue for Range {} impl TryConvert for Range { fn try_convert(val: Value) -> Result { Self::from_value(val).ok_or_else(|| { Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into Range", unsafe { val.classname() },), ) }) } } magnus-0.7.1/src/rb_sys.rs000064400000000000000000000156321046102023000135700ustar 00000000000000//! Functions for interoperability with rb-sys. //! //! These functions are provided to interface with the lower-level Ruby //! bindings provided by [rb-sys](rb_sys). You may want to use rb-sys when: //! //! 1. Magnus does not provide access to a Ruby API because the API can not be //! made safe & ergonomic. //! 2. Magnus exposed the API in a way that does not work for your use case. //! 3. The API just hasn't been implemented yet. //! //! Even if you are not in a position to contribute code to Magnus, please //! [open an issue](https://github.com/matsadler/magnus/issues) outlining your //! use case and the APIs you need whenever you find yourself reaching for this //! module. //! //! # Stability //! //! Functions in this module are considered unstable. While there is no plan //! to alter or remove them, non-backwards compatible changes in this module //! will not necessarily be considered as SemVer major changes. //! //! # Safety //! //! The unsafe functions in this module are capable of producing values that //! break the saftey guarantees of almost every other function in Magnus. Use //! them with care. use std::panic::UnwindSafe; use rb_sys::{ID, VALUE}; use crate::{ error::{self, raise, Error}, value::{Id, ReprValue, Value}, }; /// Converts from a [`Value`] to a raw [`VALUE`]. pub trait AsRawValue { /// Convert [`magnus::Value`](Value) to [`rb_sys::VALUE`]. /// /// # Examples /// /// ``` /// use magnus::{ /// rb_sys::{protect, AsRawValue}, /// Error, Ruby, /// }; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let foo = ruby.str_new("foo"); /// let bar = ruby.str_new("bar"); /// /// protect(|| unsafe { rb_sys::rb_str_buf_append(foo.as_raw(), bar.as_raw()) })?; /// /// assert_eq!(foo.to_string()?, "foobar"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn as_raw(self) -> VALUE; } /// Converts from a raw [`VALUE`] to a [`Value`]. pub trait FromRawValue { /// Convert [`rb_sys::VALUE`] to [`magnus::Value`](Value). /// /// # Safety /// /// You must only supply a valid [`VALUE`] obtained from [rb-sys](rb_sys) /// to this function. Using a invalid [`Value`] produced from this /// function will void all saftey guarantees provided by Magnus. /// /// # Examples /// /// ``` /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// use magnus::{rb_sys::FromRawValue, Value}; /// /// let raw_value = unsafe { rb_sys::rb_str_new("foo".as_ptr() as *mut _, 3) }; /// /// assert_eq!(unsafe { Value::from_raw(raw_value) }.to_string(), "foo"); /// ``` unsafe fn from_raw(value: VALUE) -> Self; } impl AsRawValue for T where T: ReprValue, { fn as_raw(self) -> VALUE { self.as_rb_value() } } impl FromRawValue for Value { unsafe fn from_raw(val: VALUE) -> Value { Value::new(val) } } /// Trait to convert a [`Id`] to a raw [`ID`]. pub trait AsRawId { /// Convert [`magnus::value::Id`](Id) to [`rb_sys::ID`]. /// /// # Examples /// /// ``` /// use magnus::{ /// prelude::*, /// rb_sys::{AsRawId, FromRawId}, /// value::Id, /// Error, Ruby, Symbol, /// }; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let foo: Id = ruby.to_symbol("foo").into(); /// let raw = foo.as_raw(); /// let from_raw_val: Symbol = unsafe { Id::from_raw(raw) }.into(); /// /// assert_eq!(from_raw_val.inspect(), ":foo"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn as_raw(self) -> ID; } /// Trait to convert from a raw [`ID`] to an [`Id`]. pub trait FromRawId { /// Convert [`rb_sys::ID`] to [`magnus::value::Id`](Id). /// /// # Safety /// /// You must only supply a valid, non-zero [`ID`] obtained from /// [rb-sys](rb_sys) to this function. Using a invalid [`Id`] produced /// from this function will void all saftey guarantees provided by /// Magnus. /// /// # Examples /// /// ``` /// use magnus::{ /// prelude::*, /// rb_sys::{AsRawId, FromRawId}, /// value::Id, /// Error, Ruby, Symbol, /// }; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let foo: Id = ruby.to_symbol("foo").into(); /// let from_raw_val: Symbol = unsafe { Id::from_raw(foo.as_raw()) }.into(); /// /// assert_eq!(from_raw_val.inspect(), ":foo"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` unsafe fn from_raw(id: ID) -> Self; } impl AsRawId for Id { fn as_raw(self) -> ID { self.as_rb_id() } } impl FromRawId for Id { unsafe fn from_raw(id: ID) -> Id { Id::from_rb_id(id.into()) } } /// Calls the given closure, catching all cases of unwinding from Ruby /// returning them as an [`Error`]. /// /// The most common will be exceptions, but this will also catch `throw`, /// `break`, `next`, `return` from a block, etc. /// /// All functions exposed by Magnus that call Ruby in a way that may unwind /// already use this internally, this should only be required to wrap functions /// from [rb-sys](rb_sys). pub fn protect(func: F) -> Result where F: FnOnce() -> VALUE, { error::protect(|| Value::new(func())).map(|v| v.as_rb_value()) } /// Attempts to catch cases of Rust unwinding, converting to a fatal [`Error`]. /// /// This should not be used to catch and discard panics. /// /// This function can be used to ensure Rust panics do not cross over to Ruby. /// This will convert a panic to a Ruby fatal [`Error`] that can then be used /// to safely terminate Ruby. /// /// All functions exposed by Magnus that allow Ruby to call Rust code already /// use this internally, this should only be required to wrap /// functions/closures given directly to [rb-sys](rb_sys). pub fn catch_unwind(func: F) -> Result where F: FnOnce() -> T + UnwindSafe, { std::panic::catch_unwind(func).map_err(Error::from_panic) } /// Resumes an [`Error`] previously caught by [`protect`]. /// /// All functions exposed by Magnus where it is safe to resume an error use /// this internally to automatically convert returned errors to raised /// exceptions. This should only be required to in functions/closures given /// directly to [rb-sys](rb_sys). /// /// # Safety /// /// Beware this function does not return and breaks the normal assumption that /// Rust code does not unwind during normal behaviour. This can break /// invariants in code that assumes unwinding only happens during terminating /// panics. /// /// If possible, only call this at the very end of a function/closure that is /// directly called by Ruby, not other Rust code, and ensure all other values /// in scope have been dropped before calling this function. pub unsafe fn resume_error(e: Error) -> ! { raise(e) } magnus-0.7.1/src/scan_args.rs000064400000000000000000000704651046102023000142340ustar 00000000000000//! Types and functions for complex method arguments. //! //! Ruby's APIs to define methods, exposed in magnus through functions such as //! [`define_method`](crate::module::Module::define_method) and the //! [`method`](crate::method!) macro, allow defining methods with a fixed //! number of positional arguments, or an unbounded collection of arguments as //! a slice or Ruby array. The functions in this module allow for more complex //! agument handling. //! //! Ruby arguments can be classified as follows: //! ``` text //! def example(a, b, c=nil, d=nil, *rest, e, f, g:, h:, i: nil, j: nil, **kwargs, &block) //! \__/ \__________/ \___/ \__/ \____/ \____________/ \______/ \____/ //! | | | | | | | | //! required | splat | keywords keywords keywords block //! optional | (required) (optional) (splat) //! | //! trailing //! required //! ``` //! //! The [`scan_args`] function can be used with a method defined as //! `method!(name, -1)` (i.e. receiving a slice of arguments) to implement this //! more complex functionality. //! //! The [`get_kwargs`] function is used to extract keywords from a Ruby `Hash` //! of keywords and implement the behaviour around required and optional //! keyword arguments. //! //! See also [`Ruby`](Ruby#argument-parsing). use std::{ ffi::CString, fmt, mem::transmute, ops::{Bound, RangeBounds}, os::raw::c_int, }; use rb_sys::{rb_error_arity, rb_get_kwargs, rb_scan_args, ID, VALUE}; use seq_macro::seq; use crate::{ block::Proc, error::{protect, Error}, r_array::RArray, r_hash::RHash, try_convert::{TryConvert, TryConvertOwned}, value::{private::ReprValue as _, Id, IntoId, ReprValue, Value}, Ruby, }; struct ArgSpec { required: usize, optional: usize, splat: bool, trailing: usize, keywords: bool, block: bool, } impl ArgSpec { fn new( required: usize, optional: usize, splat: bool, trailing: usize, keywords: bool, block: bool, ) -> Self { if required > 9 { panic!("'required' out of bounds, expected 0..=9, got {}", required); } if optional > 9 { panic!("'optional' out of bounds, expected 0..=9, got {}", required); } if trailing > 9 { panic!("'trailing' out of bounds, expected 0..=9, got {}", required); } Self { required, optional, splat, trailing, keywords, block, } } fn len(&self) -> usize { self.required + self.optional + self.splat as usize + self.trailing + self.keywords as usize + self.block as usize } } impl fmt::Display for ArgSpec { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.required > 0 || self.optional > 0 || self.trailing > 0 { write!(f, "{}", self.required)?; } if self.optional > 0 || (!self.splat && self.trailing > 0) { write!(f, "{}", self.optional)?; } if self.splat { write!(f, "*")?; } if self.trailing > 0 { write!(f, "{}", self.trailing)?; } if self.keywords { write!(f, ":")?; } if self.block { write!(f, "&")?; } Ok(()) } } /// Arguments returned from [`scan_args`]. pub struct Args { /// Required arguments. pub required: Req, /// Optional arguments. pub optional: Opt, /// The splat argument. pub splat: Splat, /// Trailing required arguments. pub trailing: Trail, /// Keywords arguments. pub keywords: Kw, /// This block argument. pub block: Block, } struct ScannedArgs { arg_spec: ArgSpec, args: [Value; 30], parsed: usize, } impl ScannedArgs { fn required(&self) -> &[Value] { &self.args[0..self.arg_spec.required] } fn optional(&self) -> &[Value] { let start = self.arg_spec.required; let end = start + self .arg_spec .optional .min(self.parsed - start - self.arg_spec.trailing); &self.args[start..end] } fn splat(&self) -> Option { self.arg_spec .splat .then(|| self.args[self.arg_spec.required + self.arg_spec.optional]) } fn trailing(&self) -> &[Value] { let start = self.arg_spec.required + self.arg_spec.optional + self.arg_spec.splat as usize; let end = start + self.arg_spec.trailing; &self.args[start..end] } fn keywords(&self) -> Option { self.arg_spec.keywords.then(|| { self.args[self.arg_spec.required + self.arg_spec.optional + self.arg_spec.splat as usize + self.arg_spec.trailing] }) } fn block(&self) -> Option { self.arg_spec.block.then(|| { self.args[self.arg_spec.required + self.arg_spec.optional + self.arg_spec.splat as usize + self.arg_spec.trailing + self.arg_spec.keywords as usize] }) } } mod private { use super::*; pub trait ScanArgsRequired: Sized { const LEN: usize; fn from_slice(vals: &[Value]) -> Result; } macro_rules! impl_scan_args_required { ($n:literal) => { seq!(N in 0..$n { impl<#(T~N,)*> ScanArgsRequired for (#(T~N,)*) where #(T~N: TryConvert,)* { const LEN: usize = $n; fn from_slice(vals: &[Value]) -> Result { if vals.len() == ::LEN { Ok(( #(TryConvert::try_convert(vals[N])?,)* )) } else { panic!( "unexpected arguments, expected {} got {}", ::LEN, vals.len() ); } } } }); } } seq!(N in 0..=9 { impl_scan_args_required!(N); }); pub trait ScanArgsOpt: Sized { const LEN: usize; fn from_slice(vals: &[Value]) -> Result; } macro_rules! impl_scan_args_opt { ($n:literal) => { seq!(N in 0..$n { impl<#(T~N,)*> ScanArgsOpt for (#(Option,)*) where #(T~N: TryConvert,)* { const LEN: usize = $n; fn from_slice(vals: &[Value]) -> Result { if vals.len() <= ::LEN { Ok(( #(vals.get(N) .filter(|v| !v.is_undef()) .copied() .map(TryConvert::try_convert) .transpose()?,)* )) } else { panic!( "unexpected arguments, expected {} got {}", ::LEN, vals.len() ); } } } }); } } seq!(N in 0..=9 { impl_scan_args_opt!(N); }); pub trait ScanArgsSplat: Sized { const REQ: bool; fn from_opt(val: Option) -> Result; } impl ScanArgsSplat for () { const REQ: bool = false; fn from_opt(val: Option) -> Result { if let Some(val) = val { panic!("unexpected argument {:?}", val); }; Ok(()) } } impl ScanArgsSplat for RArray { const REQ: bool = true; fn from_opt(val: Option) -> Result { TryConvert::try_convert(val.expect("expected splat")) } } impl ScanArgsSplat for Vec where T: TryConvertOwned, { const REQ: bool = true; fn from_opt(val: Option) -> Result { TryConvert::try_convert(val.expect("expected splat")) } } pub trait ScanArgsKw: Sized { const REQ: bool; fn from_opt(val: Option) -> Result; } impl ScanArgsKw for () { const REQ: bool = false; fn from_opt(val: Option) -> Result { if let Some(val) = val { panic!("unexpected argument {:?}", val); }; Ok(()) } } impl ScanArgsKw for RHash { const REQ: bool = true; fn from_opt(val: Option) -> Result { let handle = unsafe { Ruby::get_unchecked() }; let val = val.expect("expected keywords"); if val.is_nil() { return Ok(handle.hash_new()); } TryConvert::try_convert(val) } } pub trait ScanArgsBlock: Sized { const REQ: bool; fn from_opt(val: Option) -> Result; } impl ScanArgsBlock for () { const REQ: bool = false; fn from_opt(val: Option) -> Result { if let Some(val) = val { panic!("unexpected argument {:?}", val); }; Ok(()) } } impl ScanArgsBlock for Proc { const REQ: bool = true; fn from_opt(val: Option) -> Result { let val = val.expect("expected block"); if val.is_nil() { return Err(Error::new( unsafe { Ruby::get_unchecked().exception_arg_error() }, "no block given", )); } TryConvert::try_convert(val) } } impl ScanArgsBlock for Option { const REQ: bool = true; fn from_opt(val: Option) -> Result { TryConvert::try_convert(val.expect("expected block")) } } } /// Trait implemented for types that can be retrieved as required arguments by /// [`scan_args`]. /// /// This trait is implemented for `(T0,)`, `(T0, T1)`, `(T0, T1, T2)`, etc, /// through to a length of 9, where `T0`, `T1`, etc implement [`TryConvert`]. /// /// `()` also implements this trait as a placeholder indicating no required /// arguments are required. pub trait ScanArgsRequired: private::ScanArgsRequired {} impl ScanArgsRequired for T where T: private::ScanArgsRequired {} /// Trait implemented for types that can be retrieved as optional arguments by /// [`scan_args`]. /// /// This trait is implemented for `(Option,)`, `(Option, Option)`, /// etc, through to a length of 9, where `T0`, `T1`, etc implement /// [`TryConvert`]. /// /// `()` also implements this trait as a placeholder indicating no optional /// arguments are required. pub trait ScanArgsOpt: private::ScanArgsOpt {} impl ScanArgsOpt for T where T: private::ScanArgsOpt {} /// Trait implemented for types that can be retrieved a 'splat' argument by /// [`scan_args`]. /// /// This trait is implemented for `Vec` where `T` implements [`TryConvert`] /// and converts to an owned Rust value (not a handle to a Ruby object). It is /// also implemented for [`RArray`]. /// /// `()` also implements this trait as a placeholder indicating no splat /// argument is required. pub trait ScanArgsSplat: private::ScanArgsSplat {} impl ScanArgsSplat for T where T: private::ScanArgsSplat {} /// Trait implemented for types that can be retrieved as keyword arguments by /// [`scan_args`]. /// /// This trait is implemented for [`RHash`]. /// /// `()` also implements this trait as a placeholder indicating no keyword /// arguments are required. pub trait ScanArgsKw: private::ScanArgsKw {} impl ScanArgsKw for T where T: private::ScanArgsKw {} /// Trait implemented for types that can be retrieved as a block argument by /// [`scan_args`]. /// /// This trait is implemented for [`Proc`] and `Option`. /// /// `()` also implements this trait as a placeholder for when no block argument /// is required, although Ruby will still allow a block to be passed, it will /// just ignore it (as is standard for all Ruby methods). pub trait ScanArgsBlock: private::ScanArgsBlock {} impl ScanArgsBlock for T where T: private::ScanArgsBlock {} /// Retrieves arguments from a slice. /// /// This function can be used to implement Ruby methods with more complex /// signatures, including optional arguments and 'splats'. /// /// The format of the arguments required is driven by the types in the return /// value. The stuct [`Args`] is returned but the types of its fields are /// determined by type parameters. The type `()` is used as a placeholder when /// a set of arguments is not required. /// /// # Examples /// /// `TCPServer::new`'s argument handling. This is roughly equivalent to /// `def new(hostname=nil, port)`. /// /// ``` /// use magnus::{function, prelude::*, scan_args::scan_args, Error, Ruby, Value}; /// /// fn tcp_svr_init(args: &[Value]) -> Result { /// let args = scan_args(args)?; /// let _: () = args.required; /// let (hostname,): (Option,) = args.optional; /// let _: () = args.splat; /// let (port,): (u16,) = args.trailing; /// let _: () = args.keywords; /// let _: () = args.block; /// /// // ... /// # let res = Ruby::get().unwrap().ary_new_capa(2); /// # res.push(hostname)?; /// # res.push(port)?; /// # Ok(res.as_value()) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let class = ruby.define_class("TCPServer", ruby.class_object())?; /// class.define_singleton_method("new", function!(tcp_svr_init, -1))?; /// # let res = ruby.eval::(r#"TCPServer.new("foo", 1) == ["foo", 1]"#)?; /// # assert!(res); /// # let res = ruby.eval::(r#"TCPServer.new(2) == [nil, 2]"#)?; /// # assert!(res); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// The same example as above, specifying the types slightly differently. /// ``` /// use magnus::{function, prelude::*, scan_args::scan_args, Error, Ruby, Value}; /// /// fn tcp_svr_init(args: &[Value]) -> Result { /// let args = scan_args::<(), (Option,), (), (u16,), (), ()>(args)?; /// let (hostname,) = args.optional; /// let (port,) = args.trailing; /// /// // ... /// # let res = Ruby::get().unwrap().ary_new_capa(2); /// # res.push(hostname)?; /// # res.push(port)?; /// # Ok(res.as_value()) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let class = ruby.define_class("TCPServer", ruby.class_object())?; /// class.define_singleton_method("new", function!(tcp_svr_init, -1))?; /// # let res = ruby.eval::(r#"TCPServer.new("foo", 1) == ["foo", 1]"#)?; /// # assert!(res); /// # let res = ruby.eval::(r#"TCPServer.new(2) == [nil, 2]"#)?; /// # assert!(res); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// `Addrinfo::getaddrinfo`'s argument handling. This is roughly equivalent to /// `def getaddrinfo(nodename, service, family=nil, socktype=nil, protocol=nil, flags=nil, timeout: nil)`: /// /// ``` /// use magnus::{function, prelude::*, scan_args::{get_kwargs, scan_args}, Error, Ruby, Symbol, Value}; /// /// fn getaddrinfo(args: &[Value]) -> Result { /// let args = scan_args::<_, _, (), (), _, ()>(args)?; /// let (nodename, service): (String, u16) = args.required; /// let (family, socktype, protocol, flags): ( /// Option, /// Option, /// Option, /// Option, /// ) = args.optional; /// let kw = get_kwargs::<_, (), (Option,), ()>(args.keywords, &[], &["timeout"])?; /// let (timeout,) = kw.optional; /// /// // ... /// # let res = Ruby::get().unwrap().ary_new_capa(7); /// # res.push(nodename)?; /// # res.push(service)?; /// # res.push(family)?; /// # res.push(socktype)?; /// # res.push(protocol)?; /// # res.push(flags)?; /// # res.push(timeout)?; /// # Ok(res.as_value()) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let class = ruby.define_class("Addrinfo", ruby.class_object())?; /// class.define_singleton_method("getaddrinfo", function!(getaddrinfo, -1))?; /// # let res = ruby.eval::(r#"Addrinfo.getaddrinfo("a", 1) == ["a", 1, nil, nil, nil, nil, nil]"#)?; /// # assert!(res); /// # let res = ruby.eval::(r#"Addrinfo.getaddrinfo("a", 1, :b, :c, 3, 4, timeout: 5) == ["a", 1, :b, :c, 3, 4, 5]"#)?; /// # assert!(res); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn scan_args( args: &[Value], ) -> Result, Error> where Req: ScanArgsRequired, Opt: ScanArgsOpt, Splat: ScanArgsSplat, Trail: ScanArgsRequired, Kw: ScanArgsKw, Block: ScanArgsBlock, { let arg_spec = ArgSpec::new( Req::LEN, Opt::LEN, Splat::REQ, Trail::LEN, Kw::REQ, Block::REQ, ); let scanned_args = scan_args_untyped(args, arg_spec)?; Ok(Args { required: Req::from_slice(scanned_args.required())?, optional: Opt::from_slice(scanned_args.optional())?, splat: Splat::from_opt(scanned_args.splat())?, trailing: Trail::from_slice(scanned_args.trailing())?, keywords: Kw::from_opt(scanned_args.keywords())?, block: Block::from_opt(scanned_args.block())?, }) } // Nice-ish interface to rb_scan_args, but returns `Value`s without conversion. fn scan_args_untyped(args: &[Value], arg_spec: ArgSpec) -> Result { let mut out = [unsafe { Ruby::get_unchecked().qnil().as_value() }; 30]; let parsed = unsafe { scan_args_impl(args, &arg_spec.to_string(), &mut out[..arg_spec.len()])? }; Ok(ScannedArgs { arg_spec, args: out, parsed, }) } // Fairly close to rb_scan_args, but Rust types and works around variadic args. // Size of `out` must be >= number of arguments specified in `fmt`. unsafe fn scan_args_impl(args: &[Value], fmt: &str, out: &mut [Value]) -> Result { let out: &mut [VALUE] = transmute(out); let argc = args.len() as c_int; let argv = args.as_ptr() as *const VALUE; let cstr = CString::new(fmt).unwrap(); let fmt = cstr.as_ptr(); let mut result = 0; let handle = Ruby::get_unchecked(); macro_rules! match_arm { ($n:literal) => { seq!(N in 0..$n { protect(|| { result = rb_scan_args( argc, argv, fmt, #(&mut out[N] as *mut VALUE,)* ) as usize; handle.qnil() })? }) } } seq!(N in 0..30 { match out.len() { #(N => match_arm!(N),)* _ => unreachable!(), } }); Ok(result) } /// Arguments returned from [`get_kwargs`]. pub struct KwArgs { /// Required arguments. pub required: Req, /// Optional arguments. pub optional: Opt, /// The splat argument. pub splat: Splat, } /// Deconstruct keyword arguments. /// /// Extracts `required` and `optional` arguments from the given `kw` hash. /// /// The format of the arguments required is driven by the types in the return /// value. The stuct [`KwArgs`] is returned but the types of its fields are /// determined by type parameters. The type `()` is used as a placeholder when /// a set of arguments is not required. /// /// # Panics /// /// This function will panic if `required` or `optional` arguments don't match /// the length of the `Req` and `Opt` type parameters. /// /// # Examples /// /// The rough equivalent of `def test(a:, b:, c: nil, **rest)` would be: /// ``` /// use magnus::{method, prelude::*, scan_args::get_kwargs, Error, RHash, Ruby, Value}; /// # use magnus::IntoValue; /// /// fn test(_rb_self: Value, kw: RHash) -> Result { /// let args = get_kwargs(kw, &["a", "b"], &["c"])?; /// let (a, b): (String, usize) = args.required; /// let (c,): (Option,) = args.optional; /// let rest: RHash = args.splat; /// /// // ... /// # let res = Ruby::get().unwrap().ary_new_capa(4); /// # res.push(a)?; /// # res.push(b)?; /// # res.push(c)?; /// # res.push(rest)?; /// # Ok(res.into_value_with(&Ruby::get().unwrap())) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.class_object().define_method("test", method!(test, 1))?; /// # let res = ruby.eval::(r#"Object.new.test(a: "foo", b: 1, c: true, d: "bar") == ["foo", 1, true, {d: "bar"}]"#)?; /// # assert!(res); /// # let res = ruby.eval::(r#"Object.new.test(a: "foo", b: 1) == ["foo", 1, nil, {}]"#)?; /// # assert!(res); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// The rough equivalent of `def test(a: "foo")` would be: /// ``` /// use magnus::{ /// method, /// prelude::*, /// scan_args::{get_kwargs, scan_args}, /// Error, Ruby, Value, /// }; /// # use magnus::IntoValue; /// /// fn test(_rb_self: Value, args: &[Value]) -> Result { /// let args = scan_args::<(), (), (), (), _, ()>(args)?; /// let args = get_kwargs(args.keywords, &[], &["a"])?; /// let _: () = args.required; /// let (a,): (Option,) = args.optional; /// let _: () = args.splat; /// let a = a.unwrap_or_else(|| String::from("foo")); /// /// // ... /// # Ok(a.into_value_with(&Ruby::get().unwrap())) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.class_object() /// .define_method("test", method!(test, -1))?; /// # let res = ruby.eval::(r#"Object.new.test(a: "test") == "test""#)?; /// # assert!(res); /// # let res = ruby.eval::(r#"Object.new.test == "foo""#)?; /// # assert!(res); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// or, specifying the types slightly differently: /// ``` /// use magnus::{ /// method, /// prelude::*, /// scan_args::{get_kwargs, scan_args}, /// Error, RHash, Ruby, Value, /// }; /// # use magnus::IntoValue; /// /// fn test(_rb_self: Value, args: &[Value]) -> Result { /// let args = scan_args::<(), (), (), (), RHash, ()>(args)?; /// let args = get_kwargs::<_, (), (Option,), ()>(args.keywords, &[], &["a"])?; /// let (a,) = args.optional; /// let a = a.unwrap_or_else(|| String::from("foo")); /// /// // ... /// # Ok(a.into_value_with(&Ruby::get().unwrap())) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.class_object() /// .define_method("test", method!(test, -1))?; /// # let res = ruby.eval::(r#"Object.new.test(a: "test") == "test""#)?; /// # assert!(res); /// # let res = ruby.eval::(r#"Object.new.test == "foo""#)?; /// # assert!(res); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn get_kwargs( kw: RHash, required: &[T], optional: &[T], ) -> Result, Error> where T: IntoId + Copy, Req: ScanArgsRequired, Opt: ScanArgsOpt, Splat: ScanArgsKw, { assert_eq!(required.len(), Req::LEN); assert_eq!(optional.len(), Opt::LEN); let handle = Ruby::get_with(kw); let ids = required .iter() .copied() .map(|id| id.into_id_with(&handle)) .chain(optional.iter().copied().map(|id| id.into_id_with(&handle))) .collect::>(); let optional_len = if Splat::REQ { -(optional.len() as i8 + 1) } else { optional.len() as i8 }; let mut out = [unsafe { Ruby::get_unchecked().qnil().as_value() }; 19]; let total = Req::LEN + Opt::LEN + Splat::REQ as usize; let mut parsed = 0; unsafe { protect(|| { parsed = rb_get_kwargs( kw.as_rb_value(), ids.as_ptr() as *const ID, required.len() as c_int, optional_len as c_int, out[..total].as_mut_ptr() as *mut VALUE, ) as usize; Ruby::get_unchecked().qnil() })?; }; let opt_end = Req::LEN + Opt::LEN; Ok(KwArgs { required: Req::from_slice(&out[..Req::LEN])?, optional: Opt::from_slice(&out[Req::LEN..opt_end])?, splat: Splat::from_opt(Splat::REQ.then(|| handle.into_value(kw)))?, }) } /// # Argument Parsing /// /// Functions for handling argument parsing. /// /// See also the [`scan_args`] module. impl Ruby { /// Returns `Err` containing a Ruby `ArgumentError` if `len` is not within /// `bounds`. /// /// # Examples /// /// ``` /// use magnus::{function, Error, RString, Ruby, Value}; /// /// fn test(ruby: &Ruby, args: &[Value]) -> Result { /// ruby.check_arity(args.len(), 2..5)?; /// ruby.ary_new_from_values(args).join(", ") /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_function("test", function!(test, -1)); /// /// assert_eq!( /// ruby.eval::("test(1)").unwrap_err().to_string(), /// "wrong number of arguments (given 1, expected 2..4)" /// ); /// assert_eq!( /// ruby.eval::("test(1, 2, 3, 4, 5)") /// .unwrap_err() /// .to_string(), /// "wrong number of arguments (given 5, expected 2..4)" /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn check_arity(&self, len: usize, bounds: T) -> Result<(), Error> where T: RangeBounds, { if !bounds.contains(&len) { let min = match bounds.start_bound() { Bound::Included(v) => *v as c_int, Bound::Excluded(_) => unreachable!(), Bound::Unbounded => 0, }; let max = match bounds.end_bound() { Bound::Included(v) => *v as c_int, Bound::Excluded(v) if *v == 0 => 0, Bound::Excluded(v) => (v - 1) as c_int, Bound::Unbounded => -1, }; protect(|| { unsafe { rb_error_arity(len as c_int, min, max) }; // we never get here, but this is needed to satisfy the type // system #[allow(unreachable_code)] self.qnil() })?; } Ok(()) } } /// Returns `Err` containing a Ruby `ArgumentError` if `len` is not within /// `bounds`. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::check_arity`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{ /// define_global_function, eval, function, scan_args::check_arity, Error, RArray, RString, /// Value, /// }; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// fn example(args: &[Value]) -> Result { /// check_arity(args.len(), 2..5)?; /// RArray::from_slice(args).join(", ") /// } /// /// define_global_function("example", function!(example, -1)); /// /// assert_eq!( /// eval::("example(1)").unwrap_err().to_string(), /// "wrong number of arguments (given 1, expected 2..4)" /// ); /// assert_eq!( /// eval::("example(1, 2, 3, 4, 5)") /// .unwrap_err() /// .to_string(), /// "wrong number of arguments (given 5, expected 2..4)" /// ); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::check_arity` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn check_arity(len: usize, bounds: T) -> Result<(), Error> where T: RangeBounds, { get_ruby!().check_arity(len, bounds) } magnus-0.7.1/src/symbol.rs000064400000000000000000000260431046102023000135720ustar 00000000000000//! Types and traits for working with Ruby symbols. use std::{borrow::Cow, fmt}; use rb_sys::{rb_check_id, rb_intern_str, rb_sym2str, rb_to_symbol, ruby_value_type, VALUE}; use crate::{ encoding::EncodingCapable, error::{protect, Error}, into_value::IntoValue, r_string::RString, try_convert::TryConvert, value::{ private::{self, ReprValue as _}, Id, LazyId, NonZeroValue, OpaqueId, ReprValue, StaticSymbol, Value, }, Ruby, }; /// # Symbol /// /// Functions that can be used to create Ruby `Symbol`s. /// /// See also the [`Symbol`] type. impl Ruby { /// Create a new `Symbol` from `name`. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let sym = ruby.to_symbol("example"); /// rb_assert!(ruby, ":example == sym", sym); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn to_symbol>(&self, name: T) -> Symbol { name.as_ref().into_symbol_with(self) } } /// A type wrapping either a [`StaticSymbol`] or a Value pointer to a RSymbol /// struct. /// /// See the [`ReprValue`] trait for additional methods available on this type. /// See [`Ruby`](Ruby#symbol) for methods to create a `Symbol`. #[derive(Clone, Copy, Eq, PartialEq)] #[repr(transparent)] pub struct Symbol(NonZeroValue); impl Symbol { /// Return `Some(Symbol)` if `val` is a `Symbol`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, Symbol}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(Symbol::from_value(eval(":foo").unwrap()).is_some()); /// assert!(Symbol::from_value(eval(r#""bar".to_sym"#).unwrap()).is_some()); /// assert!(Symbol::from_value(eval(r#""baz""#).unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { if val.is_static_symbol() { return Some(Self(NonZeroValue::new_unchecked(val))); } debug_assert_value!(val); (val.rb_type() == ruby_value_type::RUBY_T_SYMBOL) .then(|| Self(NonZeroValue::new_unchecked(val))) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } /// Create a new `Symbol` from `name`. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::to_symbol`] for /// the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{rb_assert, Symbol}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let sym = Symbol::new("example"); /// rb_assert!(":example == sym", sym); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::to_symbol` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn new>(name: T) -> Self { get_ruby!().to_symbol(name) } /// Returns whether `self` is static or not. /// /// Static symbols won't be garbage collected, so should be safe to store /// on the heap. See [`StaticSymbol`]. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby, Symbol}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ruby.eval::(":foo")?.is_static()); /// assert!(!ruby.to_symbol("bar").is_static()); /// assert!(!ruby.eval::(r#""baz".to_sym"#)?.is_static()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn is_static(self) -> bool { if self.is_static_symbol() { return true; } let mut p = self.as_rb_value(); unsafe { rb_check_id(&mut p as *mut _) != 0 } } /// Return the symbol as a string. If the symbol is static this will be a /// `&str`, otherwise an owned `String`. /// /// May error if the name is not valid utf-8. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let sym = ruby.to_symbol("example"); /// assert_eq!(sym.name()?, "example"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn name(self) -> Result, Error> { if let Some(sym) = self.as_static() { return sym.name().map(Cow::from); } unsafe { RString::from_rb_value_unchecked(rb_sym2str(self.as_rb_value())) .to_string() .map(Cow::from) } } /// If `self` is static, returns `self` as a [`StaticSymbol`], otherwise /// returns `None`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby, Symbol}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ruby.eval::(":foo")?.as_static().is_some()); /// assert!(ruby.to_symbol("bar").as_static().is_none()); /// assert!(ruby /// .eval::(r#""baz".to_sym"#)? /// .as_static() /// .is_none()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn as_static(self) -> Option { self.is_static() .then(|| unsafe { StaticSymbol::from_rb_value_unchecked(self.as_rb_value()) }) } /// If `self` is already static simply returns `self` as a /// [`StaticSymbol`]. If `self` is not static it will be made so and /// returned as a [`StaticSymbol`]. /// /// Be aware that once static a symbol will never be garbage collected. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let sym = ruby.to_symbol("example"); /// let static_sym = sym.to_static(); /// rb_assert!(ruby, "sym == static_sym", sym, static_sym); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn to_static(self) -> StaticSymbol { if let Some(sym) = StaticSymbol::from_value(self.as_value()) { return sym; } unsafe { let name = rb_sym2str(self.as_rb_value()); Id::from_rb_id(rb_intern_str(name)).into() } } } impl fmt::Display for Symbol { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for Symbol { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl EncodingCapable for Symbol {} /// Conversions from Rust types into [`Symbol`]. pub trait IntoSymbol: Sized { /// Convert `self` into [`Symbol`]. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`IntoSymbol::into_symbol_with`] for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{rb_assert, symbol::IntoSymbol}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let sym = "example".into_symbol(); /// rb_assert!("sym == :example", sym); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `IntoSymbol::into_symbol_with` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] fn into_symbol(self) -> Symbol { self.into_symbol_with(&get_ruby!()) } /// Convert `self` into [`Symbol`]. /// /// # Safety /// /// This method should only be called from a Ruby thread. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, symbol::IntoSymbol}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// // only safe when called from a Ruby thread /// let sym = unsafe { "example".into_symbol_unchecked() }; /// rb_assert!("sym == :example", sym); /// ``` unsafe fn into_symbol_unchecked(self) -> Symbol { self.into_symbol_with(&Ruby::get_unchecked()) } /// Convert `self` into [`Symbol`]. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, symbol::IntoSymbol, Ruby}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let ruby = Ruby::get().unwrap(); /// let sym = "example".into_symbol_with(&ruby); /// rb_assert!(ruby, "sym == :example", sym); /// ``` fn into_symbol_with(self, handle: &Ruby) -> Symbol; } impl IntoSymbol for Symbol { fn into_symbol_with(self, _: &Ruby) -> Symbol { self } } impl IntoSymbol for Id { fn into_symbol_with(self, handle: &Ruby) -> Symbol { StaticSymbol::from(self).into_symbol_with(handle) } } impl From for Symbol { fn from(id: Id) -> Self { unsafe { id.into_symbol_unchecked() } } } impl IntoSymbol for &str { fn into_symbol_with(self, handle: &Ruby) -> Symbol { protect(|| unsafe { Symbol::from_rb_value_unchecked(rb_to_symbol(handle.str_new(self).as_rb_value())) }) .unwrap() } } impl IntoSymbol for String { fn into_symbol_with(self, handle: &Ruby) -> Symbol { self.as_str().into_symbol_with(handle) } } impl IntoSymbol for StaticSymbol { fn into_symbol_with(self, handle: &Ruby) -> Symbol { unsafe { Symbol(NonZeroValue::new_unchecked(self.into_value_with(handle))) } } } impl From for Symbol { fn from(s: StaticSymbol) -> Self { s.into_symbol_with(&Ruby::get_with(s)) } } impl IntoValue for Symbol { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl PartialEq for Symbol { fn eq(&self, other: &Id) -> bool { self.as_static().map(|s| s == *other).unwrap_or(false) } } impl PartialEq for Symbol { fn eq(&self, other: &OpaqueId) -> bool { self.as_static().map(|s| s == *other).unwrap_or(false) } } impl PartialEq for Symbol { /// # Panics /// /// Panics if the first call is from a non-Ruby thread. The `LazyId` will /// then be *poisoned* and all future use of it will panic. fn eq(&self, other: &LazyId) -> bool { self.as_static().map(|s| s == *other).unwrap_or(false) } } impl PartialEq for Symbol { fn eq(&self, other: &StaticSymbol) -> bool { self.as_static().map(|s| s == *other).unwrap_or(false) } } unsafe impl private::ReprValue for Symbol {} impl ReprValue for Symbol {} impl TryConvert for Symbol { fn try_convert(val: Value) -> Result { Self::from_value(val).ok_or_else(|| { Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into Symbol", unsafe { val.classname() },), ) }) } } magnus-0.7.1/src/thread.rs000064400000000000000000000500571046102023000135360ustar 00000000000000use std::{fmt, mem::size_of, os::raw::c_void, slice, time::Duration}; use rb_sys::{ rb_data_typed_object_wrap, rb_thread_alone, rb_thread_check_ints, rb_thread_create, rb_thread_current, rb_thread_fd_close, rb_thread_fd_writable, rb_thread_interrupted, rb_thread_kill, rb_thread_local_aref, rb_thread_local_aset, rb_thread_main, rb_thread_run, rb_thread_schedule, rb_thread_sleep_deadly, rb_thread_sleep_forever, rb_thread_wait_fd, rb_thread_wait_for, rb_thread_wakeup, rb_thread_wakeup_alive, timeval, VALUE, }; use crate::{ api::Ruby, data_type_builder, error::{protect, Error}, gc, into_value::IntoValue, method::{BlockReturn, Thread as _}, object::Object, r_file::fd::AsRawFd, r_typed_data::RTypedData, try_convert::TryConvert, typed_data::{DataType, DataTypeFunctions}, value::{ private::{self, ReprValue as _}, IntoId, ReprValue, Value, }, }; /// # `Thread` /// /// Functions to create and work with Ruby `Thread`s. /// /// See also the [`Thread`] type. impl Ruby { /// Create a Ruby thread. /// /// As `func` is a function pointer, only functions and closures that do /// not capture any variables are permitted. For more flexibility (at the /// cost of allocating) see /// [`thread_create_from_fn`](Ruby::thread_create_from_fn). /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let t = ruby.thread_create(|_ruby| 1 + 2); /// rb_assert!("t.value == 3", t); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn thread_create(&self, func: fn(&Ruby) -> R) -> Thread where R: BlockReturn, { unsafe extern "C" fn call(arg: *mut c_void) -> VALUE where R: BlockReturn, { let func = std::mem::transmute::<*mut c_void, fn(&Ruby) -> R>(arg); func.call_handle_error().as_rb_value() } let call_func = call:: as unsafe extern "C" fn(arg: *mut c_void) -> VALUE; unsafe { protect(|| { Thread::from_rb_value_unchecked(rb_thread_create( Some(call_func), func as *mut c_void, )) }) .unwrap() } } /// Create a Ruby thread. /// /// See also [`thread_create`](Ruby::thread_create), which is more /// efficient when `func` is a function or closure that does not /// capture any variables. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let i = 1; /// let t = ruby.thread_create_from_fn(move |_ruby| i + 2); /// rb_assert!("t.value == 3", t); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn thread_create_from_fn(&self, func: F) -> Thread where F: 'static + Send + FnOnce(&Ruby) -> R, R: BlockReturn, { unsafe extern "C" fn call(arg: *mut c_void) -> VALUE where F: FnOnce(&Ruby) -> R, R: BlockReturn, { let closure = (*(arg as *mut Option)).take().unwrap(); closure.call_handle_error().as_rb_value() } let (closure, keepalive) = wrap_closure(func); let call_func = call:: as unsafe extern "C" fn(arg: *mut c_void) -> VALUE; let t = unsafe { protect(|| { Thread::from_rb_value_unchecked(rb_thread_create( Some(call_func), closure as *mut c_void, )) }) .unwrap() }; // ivar without @ prefix is invisible from Ruby t.ivar_set("__rust_closure", keepalive).unwrap(); t } /// Return the currently executing thread. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let t = ruby.thread_current(); /// t.is_kind_of(ruby.class_thread()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn thread_current(&self) -> Thread { unsafe { Thread::from_rb_value_unchecked(rb_thread_current()) } } /// Return the 'main' thread. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let t = ruby.thread_main(); /// t.is_kind_of(ruby.class_thread()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn thread_main(&self) -> Thread { unsafe { Thread::from_rb_value_unchecked(rb_thread_main()) } } /// Attempt to schedule another thread. /// /// This function blocks until the current thread is re-scheduled. pub fn thread_schedule(&self) { unsafe { rb_thread_schedule() }; } /// Blocks until the given file descriptor is readable. /// /// # Examples /// /// ``` /// # #[cfg(unix)] /// # { /// use std::{ /// io::{Read, Write}, /// net::Shutdown, /// os::unix::net::UnixStream, /// }; /// /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let (mut a, mut b) = UnixStream::pair().unwrap(); /// a.write_all(b"hello, world!").unwrap(); /// a.shutdown(Shutdown::Both).unwrap(); /// /// b.set_nonblocking(true).unwrap(); /// ruby.thread_wait_fd(&b)?; /// /// let mut s = String::new(); /// b.read_to_string(&mut s).unwrap(); /// assert_eq!(s, "hello, world!"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// # } /// ``` pub fn thread_wait_fd(&self, fd: &T) -> Result<(), Error> where T: AsRawFd, { let fd = fd.as_raw_fd(); protect(|| { unsafe { rb_thread_wait_fd(fd) }; self.qnil() })?; Ok(()) } /// Blocks until the given file descriptor is writable. /// /// # Examples /// /// ``` /// # #[cfg(unix)] /// # { /// use std::{ /// io::{Read, Write}, /// net::Shutdown, /// os::unix::net::UnixStream, /// }; /// /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let (mut a, mut b) = UnixStream::pair().unwrap(); /// /// a.set_nonblocking(true).unwrap(); /// ruby.thread_fd_writable(&a)?; /// a.write_all(b"hello, world!").unwrap(); /// a.shutdown(Shutdown::Both).unwrap(); /// /// let mut s = String::new(); /// b.read_to_string(&mut s).unwrap(); /// assert_eq!(s, "hello, world!"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// # } /// ``` pub fn thread_fd_writable(&self, fd: &T) -> Result<(), Error> where T: AsRawFd, { let fd = fd.as_raw_fd(); protect(|| { unsafe { rb_thread_fd_writable(fd) }; self.qnil() })?; Ok(()) } /// Schedules any Ruby threads waiting on `fd`, notifying them that `fd` /// has been closed. /// /// Blocks until all threads waiting on `fd` have woken up. pub fn thread_fd_close(&self, fd: &T) -> Result<(), Error> where T: AsRawFd, { let fd = fd.as_raw_fd(); protect(|| { unsafe { rb_thread_fd_close(fd) }; self.qnil() })?; Ok(()) } /// Checks if the current thread is the only thread currently alive. /// /// # Examples /// /// ``` /// use std::time::Duration; /// /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ruby.thread_alone()); /// /// ruby.thread_create_from_fn(|ruby| ruby.thread_sleep(Duration::from_secs(1))); /// /// assert!(!ruby.thread_alone()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn thread_alone(&self) -> bool { unsafe { rb_thread_alone() != 0 } } /// Blocks for the given period of time. /// /// Returns an error if sleep is intrrupted by a signal. /// /// # Examples /// /// ``` /// use std::time::{Duration, Instant}; /// /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let now = Instant::now(); /// ruby.thread_sleep(Duration::from_millis(100))?; /// let elapsed = now.elapsed(); /// assert!(elapsed.as_millis() > 90); /// assert!(elapsed.as_secs() < 1); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn thread_sleep(&self, duration: Duration) -> Result<(), Error> { let t = timeval { tv_sec: duration.as_secs() as _, tv_usec: duration.subsec_micros() as _, }; protect(|| { unsafe { rb_thread_wait_for(t) }; self.qnil() })?; Ok(()) } /// Blocks indefinitely. /// /// Returns an error if sleep is intrrupted by a signal. pub fn thread_sleep_forever(&self) -> Result<(), Error> { protect(|| { unsafe { rb_thread_sleep_forever() }; self.qnil() })?; Ok(()) } /// Blocks indefinitely. /// /// The thread calling this function is considered "dead" when Ruby's /// deadlock checker is triggered. /// See also [`thread_sleep_forever`](Ruby::thread_sleep_forever). /// /// Returns an error if sleep is intrrupted by a signal. pub fn thread_sleep_deadly(&self) -> Result<(), Error> { protect(|| { unsafe { rb_thread_sleep_deadly() }; self.qnil() })?; Ok(()) } /// Stop the current thread. /// /// The thread can later be woken up, see [`Thread::wakeup`]. /// /// Returns an error if stopping the current thread would deadlock. pub fn thread_stop(&self) -> Result<(), Error> { protect(|| { unsafe { rb_thread_sleep_forever() }; self.qnil() })?; Ok(()) } /// 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(()) } } /// Wrapper type for a Value known to be an instance of Ruby's Thread class. /// /// See the [`ReprValue`] and [`Object`] traits for additional methods /// available on this type. See [`Ruby`](Ruby#thread) for methods to create a /// `Thread`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct Thread(RTypedData); impl Thread { /// Return `Some(Thread)` if `val` is a `Thread`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::eval; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(magnus::Thread::from_value(eval("Thread.new {1 + 2}").unwrap()).is_some()); /// assert!(magnus::Thread::from_value(eval("Proc.new {1 + 2}").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { RTypedData::from_value(val) .filter(|_| val.is_kind_of(Ruby::get_with(val).class_thread())) .map(Self) } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(RTypedData::from_rb_value_unchecked(val)) } /// Mark `self` as eligible for scheduling. /// /// See also [`Thread::wakeup_alive`] and [`Thread::run`]. /// /// The thread is not scheduled immediately, simply marked as available. /// The thread may also remain blocked on IO. /// /// Returns an error `self` is dead. pub fn wakeup(self) -> Result<(), Error> { let ruby = Ruby::get_with(self); protect(|| { unsafe { rb_thread_wakeup(self.as_rb_value()) }; ruby.qnil() })?; Ok(()) } /// Mark `self` as eligible for scheduling. /// /// See also [`Thread::wakeup`] and [`Thread::run`]. /// /// The thread is not scheduled immediately, simply marked as available. /// The thread may also remain blocked on IO. pub fn wakeup_alive(self) { unsafe { rb_thread_wakeup_alive(self.as_rb_value()) }; } /// Mark `self` as eligible for scheduling and invoke the thread schedular. /// /// See also [`Thread::wakeup`] and [`Thread::wakeup_alive`]. /// /// There is not gurantee that `self` will be the next thread scheduled. /// /// Returns an error `self` is dead. pub fn run(self) -> Result<(), Error> { let ruby = Ruby::get_with(self); protect(|| { unsafe { rb_thread_run(self.as_rb_value()) }; ruby.qnil() })?; Ok(()) } /// Terminates `self`. /// /// Returns an error if the `self` is the current or main thread, returning /// this error to Ruby will end the process. pub fn kill(self) -> Result<(), Error> { let ruby = Ruby::get_with(self); protect(|| { unsafe { rb_thread_kill(self.as_rb_value()) }; ruby.qnil() })?; Ok(()) } /// Get the value for `key` from the Fiber-local storage of the Fiber /// currently executing on the thread `self`. /// /// When Fibers were added to Ruby this method became Fiber-local. If only /// a single Fiber is run on a thread then this acts exactly like /// thread-local storage. Ruby's C API does not expose true thread local /// storage. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let current = ruby.thread_current(); /// let val: Option = current.local_aref("example")?; /// assert!(val.is_none()); /// /// let other = ruby.thread_create(|ruby| { /// ruby.thread_stop()?; /// /// let val: String = ruby.thread_current().local_aref("example")?; /// assert_eq!(val, "test"); /// /// Ok(()) /// }); /// /// current.local_aset("example", "foo")?; /// other.local_aset("example", "test")?; /// /// let val: String = current.local_aref("example")?; /// assert_eq!(val, "foo"); /// /// other.run()?; /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn local_aref(self, key: I) -> Result where I: IntoId, T: TryConvert, { T::try_convert(Value::new(unsafe { rb_thread_local_aref( self.as_rb_value(), key.into_id_with(&Ruby::get_with(self)).as_rb_id(), ) })) } /// Set the value for `key` from the Fiber-local storage of the Fiber /// currently executing on the thread `self`. /// /// Returns `Err` if `self` is frozen. /// /// When Fibers were added to Ruby this method became Fiber-local. If only /// a single Fiber is run on a thread then this acts exactly like /// thread-local storage. Ruby's C API does not expose true thread local /// storage. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let current = ruby.thread_current(); /// let val: Option = current.local_aref("example")?; /// assert!(val.is_none()); /// /// let other = ruby.thread_create(|ruby| { /// ruby.thread_stop()?; /// /// let val: String = ruby.thread_current().local_aref("example")?; /// assert_eq!(val, "test"); /// /// Ok(()) /// }); /// /// current.local_aset("example", "foo")?; /// other.local_aset("example", "test")?; /// /// let val: String = current.local_aref("example")?; /// assert_eq!(val, "foo"); /// /// other.run()?; /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn local_aset(self, key: I, val: T) -> Result<(), Error> where I: IntoId, T: IntoValue, { let ruby = Ruby::get_with(self); let key = key.into_id_with(&ruby); let val = val.into_value_with(&ruby); protect(|| { unsafe { rb_thread_local_aset(self.as_rb_value(), key.as_rb_id(), val.as_rb_value()) }; ruby.qnil() })?; Ok(()) } /// Check if `self` has been interrupted. /// /// Returns true if the thread was interrupted, false otherwise. This can /// be used to detect spurious wakeups. pub fn interrupted(self) -> bool { unsafe { rb_thread_interrupted(self.as_rb_value()) != 0 } } } impl fmt::Display for Thread { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for Thread { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for Thread { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.as_value() } } impl Object for Thread {} unsafe impl private::ReprValue for Thread {} impl ReprValue for Thread {} impl TryConvert for Thread { fn try_convert(val: Value) -> Result { Self::from_value(val).ok_or_else(|| { Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into Thread", unsafe { val.classname() },), ) }) } } /// Wrap a closure in a Ruby object with no class. /// /// This effectivly makes the closure's lifetime managed by Ruby. It will be /// dropped when the returned `Value` is garbage collected. fn wrap_closure(func: F) -> (*mut Option, Value) where F: FnOnce(&Ruby) -> R, R: BlockReturn, { struct Closure(Option, DataType); unsafe impl Send for Closure {} impl DataTypeFunctions for Closure { fn mark(&self, marker: &gc::Marker) { // Attempt to mark any Ruby values captured in a closure. // Rust's closures are structs that contain all the values they // have captured. This reads that struct as a slice of VALUEs and // calls rb_gc_mark_locations which calls gc_mark_maybe which // marks VALUEs and ignores non-VALUEs marker.mark_slice(unsafe { slice::from_raw_parts( &self.0 as *const _ as *const Value, size_of::() / size_of::(), ) }); } } let data_type = data_type_builder!(Closure, "rust closure") .free_immediately() .mark() .build(); let boxed = Box::new(Closure(Some(func), data_type)); let ptr = Box::into_raw(boxed); let value = unsafe { Value::new(rb_data_typed_object_wrap( 0, // using 0 for the class will hide the object from ObjectSpace ptr as *mut _, (*ptr).1.as_rb_data_type() as *const _, )) }; unsafe { (&mut (*ptr).0 as *mut Option, value) } } magnus-0.7.1/src/time.rs000064400000000000000000000123351046102023000132220ustar 00000000000000use std::{ fmt, time::{Duration, SystemTime}, }; use rb_sys::{rb_time_new, rb_time_timeval, rb_time_utc_offset, timeval, VALUE}; use crate::{ api::Ruby, error::{protect, Error}, into_value::IntoValue, object::Object, r_typed_data::RTypedData, try_convert::TryConvert, value::{ private::{self, ReprValue as _}, Fixnum, ReprValue, Value, }, }; /// # `Time` /// /// Functions to create and work with Ruby `Time` objects. /// /// See also the [`Time`] type. impl Ruby { /// Create a new `Time` in the local timezone. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let t = ruby.time_new(1654013280, 0)?; /// /// rb_assert!(ruby, r#"t == Time.new(2022, 5, 31, 9, 8, 0, "-07:00")"#, t); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn time_new(&self, seconds: i64, microseconds: i64) -> Result { protect(|| unsafe { Time::from_rb_value_unchecked(rb_time_new( seconds.try_into().unwrap(), microseconds.try_into().unwrap(), )) }) } } /// Wrapper type for a Value known to be an instance of Ruby's Time class. /// /// See the [`ReprValue`] and [`Object`] traits for additional methods /// available on this type. See [`Ruby`](Ruby#time) for methods to create a /// `Time`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct Time(RTypedData); impl Time { /// Return `Some(Time)` if `val` is a `Time`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::eval; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(magnus::Time::from_value(eval("Time.now").unwrap()).is_some()); /// assert!(magnus::Time::from_value(eval("0").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { RTypedData::from_value(val) .filter(|_| val.is_kind_of(Ruby::get_with(val).class_time())) .map(Self) } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(RTypedData::from_rb_value_unchecked(val)) } /// Returns the timezone offset of `self` from UTC in seconds. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby, Time}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let t: Time = ruby.eval(r#"Time.new(2022, 5, 31, 9, 8, 0, "-07:00")"#)?; /// /// assert_eq!(t.utc_offset(), -25_200); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn utc_offset(self) -> i64 { unsafe { Fixnum::from_rb_value_unchecked(rb_time_utc_offset(self.as_rb_value())).to_i64() } } } impl fmt::Display for Time { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for Time { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for Time { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.as_value() } } impl IntoValue for SystemTime { #[inline] fn into_value_with(self, ruby: &Ruby) -> Value { match self.duration_since(Self::UNIX_EPOCH) { Ok(duration) => ruby .time_new( duration.as_secs().try_into().unwrap(), duration.subsec_micros().try_into().unwrap(), ) .unwrap() .as_value(), Err(_) => { let duration = Self::UNIX_EPOCH.duration_since(self).unwrap(); ruby.time_new( -i64::try_from(duration.as_secs()).unwrap(), -i64::try_from(duration.subsec_micros()).unwrap(), ) .unwrap() .as_value() } } } } impl Object for Time {} unsafe impl private::ReprValue for Time {} impl ReprValue for Time {} impl TryConvert for Time { fn try_convert(val: Value) -> Result { Self::from_value(val).ok_or_else(|| { Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into Time", unsafe { val.classname() },), ) }) } } impl TryConvert for SystemTime { fn try_convert(val: Value) -> Result { let mut timeval = timeval { tv_sec: 0, tv_usec: 0, }; protect(|| unsafe { timeval = rb_time_timeval(val.as_rb_value()); Ruby::get_unchecked().qnil() })?; if timeval.tv_sec >= 0 && timeval.tv_usec >= 0 { let mut duration = Duration::from_secs(timeval.tv_sec as _); duration += Duration::from_micros(timeval.tv_usec as _); Ok(Self::UNIX_EPOCH + duration) } else { Err(Error::new( Ruby::get_with(val).exception_arg_error(), "time must not be negative", )) } } } magnus-0.7.1/src/try_convert.rs000064400000000000000000000170541046102023000146450ustar 00000000000000//! Traits for converting from Ruby [`Value`]s to Rust types. use std::path::PathBuf; use rb_sys::{rb_get_path, rb_num2dbl}; use seq_macro::seq; #[cfg(ruby_use_flonum)] use crate::value::Flonum; use crate::{ error::{protect, Error}, integer::Integer, r_array::RArray, r_hash::RHash, r_string::RString, value::{Fixnum, ReprValue, Value}, Ruby, }; /// Conversions from [`Value`] to Rust types. pub trait TryConvert: Sized { /// Convert `val` into `Self`. fn try_convert(val: Value) -> Result; } /// Conversions from [`Value`] to Rust types that do not contain [`Value`]. /// /// This trait is used as a bound on some implementations of [`TryConvert`] /// (for example, for [`Vec`]) to prevent heap allocated datastructures /// containing `Value`, as it is not safe to store a `Value` on the heap. /// /// # Safety /// /// This trait must not be implemented for types that contain `Value`. pub unsafe trait TryConvertOwned: TryConvert {} impl TryConvert for Option where T: TryConvert, { #[inline] fn try_convert(val: Value) -> Result { (!val.is_nil()).then(|| T::try_convert(val)).transpose() } } unsafe impl TryConvertOwned for Option where T: TryConvertOwned {} impl TryConvert for bool { #[inline] fn try_convert(val: Value) -> Result { Ok(val.to_bool()) } } unsafe impl TryConvertOwned for bool {} impl TryConvert for i8 { #[inline] fn try_convert(val: Value) -> Result { Integer::try_convert(val)?.to_i8() } } unsafe impl TryConvertOwned for i8 {} impl TryConvert for i16 { #[inline] fn try_convert(val: Value) -> Result { Integer::try_convert(val)?.to_i16() } } unsafe impl TryConvertOwned for i16 {} impl TryConvert for i32 { #[inline] fn try_convert(val: Value) -> Result { Integer::try_convert(val)?.to_i32() } } unsafe impl TryConvertOwned for i32 {} impl TryConvert for i64 { #[inline] fn try_convert(val: Value) -> Result { Integer::try_convert(val)?.to_i64() } } unsafe impl TryConvertOwned for i64 {} impl TryConvert for isize { #[inline] fn try_convert(val: Value) -> Result { Integer::try_convert(val)?.to_isize() } } unsafe impl TryConvertOwned for isize {} impl TryConvert for u8 { #[inline] fn try_convert(val: Value) -> Result { Integer::try_convert(val)?.to_u8() } } unsafe impl TryConvertOwned for u8 {} impl TryConvert for u16 { #[inline] fn try_convert(val: Value) -> Result { Integer::try_convert(val)?.to_u16() } } unsafe impl TryConvertOwned for u16 {} impl TryConvert for u32 { #[inline] fn try_convert(val: Value) -> Result { Integer::try_convert(val)?.to_u32() } } unsafe impl TryConvertOwned for u32 {} impl TryConvert for u64 { #[inline] fn try_convert(val: Value) -> Result { Integer::try_convert(val)?.to_u64() } } unsafe impl TryConvertOwned for u64 {} impl TryConvert for usize { #[inline] fn try_convert(val: Value) -> Result { Integer::try_convert(val)?.to_usize() } } unsafe impl TryConvertOwned for usize {} impl TryConvert for f32 { #[inline] fn try_convert(val: Value) -> Result { f64::try_convert(val).map(|f| f as f32) } } unsafe impl TryConvertOwned for f32 {} impl TryConvert for f64 { fn try_convert(val: Value) -> Result { if let Some(fixnum) = Fixnum::from_value(val) { return Ok(fixnum.to_isize() as f64); } #[cfg(ruby_use_flonum)] if let Some(flonum) = Flonum::from_value(val) { return Ok(flonum.to_f64()); } debug_assert_value!(val); let mut res = 0.0; protect(|| { unsafe { res = rb_num2dbl(val.as_rb_value()) }; Ruby::get_with(val).qnil() })?; Ok(res) } } unsafe impl TryConvertOwned for f64 {} impl TryConvert for String { #[inline] fn try_convert(val: Value) -> Result { debug_assert_value!(val); RString::try_convert(val)?.to_string() } } unsafe impl TryConvertOwned for String {} #[cfg(feature = "bytes")] impl TryConvert for bytes::Bytes { #[inline] fn try_convert(val: Value) -> Result { debug_assert_value!(val); Ok(RString::try_convert(val)?.to_bytes()) } } #[cfg(feature = "bytes")] unsafe impl TryConvertOwned for bytes::Bytes {} impl TryConvert for char { #[inline] fn try_convert(val: Value) -> Result { debug_assert_value!(val); RString::try_convert(val)?.to_char() } } unsafe impl TryConvertOwned for char {} impl TryConvert for Vec where T: TryConvertOwned, { #[inline] fn try_convert(val: Value) -> Result { debug_assert_value!(val); RArray::try_convert(val)?.to_vec() } } unsafe impl TryConvertOwned for Vec where T: TryConvertOwned {} impl TryConvert for [T; N] where T: TryConvert, { #[inline] fn try_convert(val: Value) -> Result { debug_assert_value!(val); RArray::try_convert(val)?.to_array() } } unsafe impl TryConvertOwned for [T; N] where T: TryConvert {} macro_rules! impl_try_convert { ($n:literal) => { seq!(N in 0..$n { impl<#(T~N,)*> TryConvert for (#(T~N,)*) where #(T~N: TryConvert,)* { fn try_convert(val: Value) -> Result { debug_assert_value!(val); let array = RArray::try_convert(val)?; let slice = unsafe { array.as_slice() }; if slice.len() != $n { return Err(Error::new( Ruby::get_with(val).exception_type_error(), concat!("expected Array of length ", $n), )); } Ok(( #(TryConvert::try_convert(slice[N])?,)* )) } } unsafe impl<#(T~N,)*> TryConvertOwned for (#(T~N,)*) where #(T~N: TryConvert,)* { } }); } } seq!(N in 1..=12 { impl_try_convert!(N); }); impl TryConvert for std::collections::HashMap where K: TryConvertOwned + Eq + std::hash::Hash, V: TryConvertOwned, { #[inline] fn try_convert(val: Value) -> Result { debug_assert_value!(val); RHash::try_convert(val)?.to_hash_map() } } unsafe impl TryConvertOwned for std::collections::HashMap where K: TryConvertOwned + Eq + std::hash::Hash, V: TryConvertOwned, { } #[cfg(unix)] impl TryConvert for PathBuf { fn try_convert(val: Value) -> Result { use std::os::unix::ffi::OsStringExt; let bytes = unsafe { let r_string = protect(|| RString::from_rb_value_unchecked(rb_get_path(val.as_rb_value())))?; r_string.as_slice().to_owned() }; Ok(std::ffi::OsString::from_vec(bytes).into()) } } #[cfg(not(unix))] impl TryConvert for PathBuf { fn try_convert(val: Value) -> Result { protect(|| unsafe { RString::from_rb_value_unchecked(rb_get_path(val.as_rb_value())) })? .to_string() .map(Into::into) } } unsafe impl TryConvertOwned for PathBuf {} magnus-0.7.1/src/typed_data.rs000064400000000000000000001240321046102023000144000ustar 00000000000000//! Types and Traits for wrapping Rust types as Ruby objects. //! //! This, along with [`RTypedData`], provides a Rust API to the //! `rb_data_typed_object_wrap` function from Ruby's C API. use std::{ collections::hash_map::DefaultHasher, ffi::{c_void, CStr}, fmt, hash::Hasher, marker::PhantomData, mem::size_of_val, ops::Deref, panic::catch_unwind, ptr, }; #[cfg(ruby_gte_3_0)] use rb_sys::rbimpl_typeddata_flags::{self, RUBY_TYPED_FREE_IMMEDIATELY, RUBY_TYPED_WB_PROTECTED}; use rb_sys::{ self, rb_data_type_struct__bindgen_ty_1, rb_data_type_t, rb_obj_reveal, rb_singleton_class_attached, rb_singleton_class_clone, size_t, VALUE, }; #[cfg(ruby_lt_3_0)] const RUBY_TYPED_FREE_IMMEDIATELY: u32 = 1; #[cfg(ruby_lt_3_0)] const RUBY_TYPED_WB_PROTECTED: u32 = rb_sys::ruby_fl_type::RUBY_FL_WB_PROTECTED as u32; use crate::{ class::RClass, error::{bug_from_panic, Error}, gc, into_value::IntoValue, object::Object, r_typed_data::RTypedData, scan_args::{get_kwargs, scan_args}, try_convert::TryConvert, value::{ private::{self, ReprValue as _}, ReprValue, Value, }, Ruby, }; /// A C struct containing metadata on a Rust type, for use with the /// `rb_data_typed_object_wrap` API. #[repr(transparent)] pub struct DataType(rb_data_type_t); impl DataType { /// Create a new `DataTypeBuilder`. /// /// `name` should be unique per wrapped type. It does not need to be a /// valid Ruby identifier. /// /// See [`data_type_builder`](macro@crate::data_type_builder) to create a /// `DataTypeBuilder` with a `'static CStr` `name` from a string literal. pub const fn builder(name: &'static CStr) -> DataTypeBuilder where T: DataTypeFunctions, { DataTypeBuilder::new(name) } #[inline] pub(crate) fn as_rb_data_type(&self) -> &rb_data_type_t { &self.0 } } unsafe impl Send for DataType {} unsafe impl Sync for DataType {} /// A helper trait used to define functions associated with a [`DataType`]. pub trait DataTypeFunctions where Self: Send + Sized, { /// Called when the Ruby wrapper object is garbage collected. /// /// This can be implemented to perform Ruby-specific clean up when your /// type is no longer referenced from Ruby, but it is likely easier to do /// this in a [`Drop`] implementation for your type. /// /// This function will always be called by Ruby on GC, it can not be opted /// out of. /// /// The default implementation simply drops `self`. /// /// If this function (or the [`Drop`] implementation for your type) call /// Ruby APIs you should not enable the `free_immediately` flag with the /// [`wrap`](macro@crate::wrap)/[`TypedData`](macro@crate::TypedData) /// macro or [`DataTypeBuilder::free_immediately`]. /// /// This function **must not** panic. The process will abort if this /// function panics. fn free(self: Box) {} /// Called when Ruby marks this object as part of garbage collection. /// /// If your type contains any Ruby values you must mark each of those /// values in this function to avoid them being garbage collected. /// /// This function is only called when the `mark` flag is set with the /// [`wrap`](macro@crate::wrap)/[`TypedData`](macro@crate::TypedData) /// macro or [`DataTypeBuilder::mark`]. /// /// The default implementation does nothing. /// /// This function **must not** panic. The process will abort if this /// function panics. fn mark(&self, #[allow(unused_variables)] marker: &gc::Marker) {} /// Called by Ruby to establish the memory size of this data, to optimise /// when garbage collection happens. /// /// This function is only called when the `size` flag is set with the /// [`wrap`](macro@crate::wrap)/[`TypedData`](macro@crate::TypedData) /// macro or [`DataTypeBuilder::mark`]. /// /// The default implementation delegates to [`std::mem::size_of_val`]. /// /// This function **must not** panic. The process will abort if this /// function panics. fn size(&self) -> usize { size_of_val(self) } /// Called during garbage collection. /// /// If your type contains any Ruby values that you have marked as moveable /// in your [`mark`](Self::mark) function, you must update them in this /// function using [`gc::Compactor::location`]. /// /// Ruby values would be concidered moveable if marked with the /// [`gc::Marker::mark_movable`] function. Other marking functions such as /// [`gc::Marker::mark`] will prevent values being moved. /// /// As it is only safe for this function to receive a shared `&self` /// reference, you must implement interior mutablility to be able to update /// values. This is very hard to do correctly, and it is recommended to /// simply avoid using [`gc::Marker::mark_movable`] and `compact`. /// /// This function is only called when the `compact` flag is set with the /// [`wrap`](macro@crate::wrap)/[`TypedData`](macro@crate::TypedData) /// macro or [`DataTypeBuilder::mark`]. /// /// The default implementation does nothing. /// /// This function **must not** panic. The process will abort if this /// function panics. fn compact(&self, #[allow(unused_variables)] compactor: &gc::Compactor) {} /// Extern wrapper for `free`. Don't define or call. /// /// # Safety /// /// `ptr` must be a vaild pointer to a `Box`, and must not be aliased /// This function will free the memory pointed to by `ptr`. /// /// This function must not panic. #[doc(hidden)] unsafe extern "C" fn extern_free(ptr: *mut c_void) { if let Err(e) = catch_unwind(|| Self::free(Box::from_raw(ptr as *mut _))) { bug_from_panic(e, "panic in DataTypeFunctions::free") } } /// Extern wrapper for `mark`. Don't define or call. /// /// # Safety /// /// `ptr` must be a vaild pointer to a `Self`, and must not be aliased. /// /// This function must not panic. #[doc(hidden)] unsafe extern "C" fn extern_mark(ptr: *mut c_void) { let marker = gc::Marker::new(); if let Err(e) = catch_unwind(|| Self::mark(&*(ptr as *mut Self), &marker)) { bug_from_panic(e, "panic in DataTypeFunctions::mark") } } /// Extern wrapper for `size`. Don't define or call. /// /// # Safety /// /// `ptr` must be a vaild pointer to a `Self`. /// /// This function must not panic. #[doc(hidden)] unsafe extern "C" fn extern_size(ptr: *const c_void) -> size_t { match catch_unwind(|| Self::size(&*(ptr as *const Self)) as size_t) { Ok(v) => v, Err(e) => bug_from_panic(e, "panic in DataTypeFunctions::size"), } } /// Extern wrapper for `compact`. Don't define or call. /// /// # Safety /// /// `ptr` must be a vaild pointer to a `Self`, and must not be aliased. /// /// This function must not panic. #[doc(hidden)] unsafe extern "C" fn extern_compact(ptr: *mut c_void) { let compactor = gc::Compactor::new(); if let Err(e) = catch_unwind(|| Self::compact(&*(ptr as *mut Self), &compactor)) { bug_from_panic(e, "panic in DataTypeFunctions::compact") } } } /// A builder for [`DataType`]. pub struct DataTypeBuilder { name: &'static CStr, mark: bool, size: bool, compact: bool, free_immediately: bool, wb_protected: bool, frozen_shareable: bool, phantom: PhantomData, } /// Create a new [`DataTypeBuilder`]. /// /// `name` should be unique per wrapped type. It does not need to be a /// valid Ruby identifier. /// /// `data_type_builder!(Example, "example")` is equivalent to /// `DataTypeBuilder::::new` with a `name` argument of `"example"` as /// a `'static CStr`. #[macro_export] macro_rules! data_type_builder { ($t:ty, $name:literal) => { $crate::typed_data::DataTypeBuilder::<$t>::new(unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(concat!($name, "\0").as_bytes()) }) }; } impl DataTypeBuilder where T: DataTypeFunctions, { /// Create a new `DataTypeBuilder`. /// /// `name` should be unique per wrapped type. It does not need to be a /// valid Ruby identifier. /// /// See [`data_type_builder`](macro@crate::data_type_builder) to create a /// `DataTypeBuilder` with a `'static CStr` `name` from a string literal. pub const fn new(name: &'static CStr) -> Self { Self { name, mark: false, size: false, compact: false, free_immediately: false, wb_protected: false, frozen_shareable: false, phantom: PhantomData, } } /// Enable using the the `mark` function from ``. pub const fn mark(mut self) -> Self { self.mark = true; self } /// Enable using the the `size` function from ``. pub const fn size(mut self) -> Self { self.size = true; self } /// Enable using the the `compact` function from ``. pub const fn compact(mut self) -> Self { self.compact = true; self } /// Enable the 'free_immediately' flag. /// /// This is safe to do as long as the `::free` /// function or `T`'s drop function don't call Ruby in any way. /// /// If safe this should be enabled as this performs better and is more /// memory efficient. pub const fn free_immediately(mut self) -> Self { self.free_immediately = true; self } /// Enable the 'write barrier protected' flag. /// /// You almost certainly don't want to enable this. pub const fn wb_protected(mut self) -> Self { self.wb_protected = true; self } /// Consume the builder and create a DataType. pub const fn build(self) -> DataType { let mut flags = 0_usize as VALUE; if self.free_immediately { flags |= RUBY_TYPED_FREE_IMMEDIATELY as VALUE; } if self.wb_protected || !self.mark { flags |= RUBY_TYPED_WB_PROTECTED as VALUE; } #[cfg(ruby_gte_3_0)] if self.frozen_shareable { flags |= rbimpl_typeddata_flags::RUBY_TYPED_FROZEN_SHAREABLE as VALUE; } let dmark = if self.mark { Some(T::extern_mark as _) } else { None }; let dfree = Some(T::extern_free as _); let dsize = if self.size { Some(T::extern_size as _) } else { None }; let dcompact = if self.compact { Some(T::extern_compact as _) } else { None }; DataType(rb_data_type_t { wrap_struct_name: self.name.as_ptr() as _, function: rb_data_type_struct__bindgen_ty_1 { dmark, dfree, dsize, dcompact, reserved: [ptr::null_mut(); 1], }, parent: ptr::null(), data: ptr::null_mut(), flags, }) } } impl DataTypeBuilder where T: DataTypeFunctions + Sync, { /// Enable the 'frozen_shareable' flag. /// /// Set this if your type is thread safe when the Ruby wrapper object is /// frozen. pub const fn frozen_shareable(mut self) -> Self { self.frozen_shareable = true; self } } /// A trait for Rust types that can be used with the /// `rb_data_typed_object_wrap` API. /// /// # Safety /// /// This trait is unsafe to implement as the fields of [`DataType`] returned by /// [`TypedData::data_type`] control low level behaviour that can go very wrong /// if set incorrectly. Implementing this trait is the only way a [`DataType`] /// can be passed to Ruby and result in safety violations, [`DataType`] is /// otherwise safe (but useless) to create. /// /// The [`TypedData`](`derive@crate::TypedData`) or [`wrap`](`crate::wrap`) /// macros can help implementing this trait more safely. pub unsafe trait TypedData where Self: Send + Sized, { /// Should return the class for the Ruby object wrapping the Rust type. /// /// This can be overridden on a case by case basis by implementing /// [`TypedData::class_for`], but the result of this function will always /// be used in error messages if a value fails to convert to `Self`. /// /// If using [`class_for`](Self::class_for) it is advised to have this /// function return the superclass of those returned by `class_for`. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, value::Lazy, RClass, Ruby, TypedData}; /// # use magnus::DataType; /// /// struct Example(); /// /// unsafe impl TypedData for Example { /// fn class(ruby: &Ruby) -> RClass { /// static CLASS: Lazy = Lazy::new(|ruby| { /// let class = ruby.define_class("Example", ruby.class_object()).unwrap(); /// class.undef_default_alloc_func(); /// class /// }); /// ruby.get_inner(&CLASS) /// } /// /// // ... /// # fn data_type() -> &'static DataType { unimplemented!() } /// } /// ``` fn class(ruby: &Ruby) -> RClass; /// Should return a static reference to a [`DataType`] with metadata about /// the wrapped type. /// /// # Examples /// /// ``` /// use magnus::{data_type_builder, DataType, DataTypeFunctions, TypedData}; /// # use magnus::{RClass, Ruby}; /// /// #[derive(DataTypeFunctions)] /// struct Example(); /// /// unsafe impl TypedData for Example { /// # fn class(_: &Ruby) -> RClass { unimplemented!() } /// // ... /// /// fn data_type() -> &'static DataType { /// static DATA_TYPE: DataType = data_type_builder!(Example, "example").build(); /// &DATA_TYPE /// } /// } /// ``` fn data_type() -> &'static DataType; /// Used to customise the class wrapping a specific value of `Self`. /// /// The provided implementation simply returns the value of /// [`TypedData::class`]. /// /// The classes returned by this function must be subclasses of /// `TypedData::class`. `TypedData::class` will always be used in error /// messages if a value fails to convert to `Self`. /// /// See also [`Obj::wrap_as`]/[`RTypedData::wrap_as`]. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, value::Lazy, RClass, Ruby, TypedData}; /// # use magnus::DataType; /// /// enum Example { /// A, /// B, /// } /// /// unsafe impl TypedData for Example { /// # fn class(_: &Ruby) -> RClass { unimplemented!() } /// # fn data_type() -> &'static DataType { unimplemented!() } /// // ... /// /// fn class_for(ruby: &Ruby, value: &Self) -> RClass { /// static A: Lazy = Lazy::new(|ruby| { /// let class = ruby.define_class("A", Example::class(ruby)).unwrap(); /// class.undef_default_alloc_func(); /// class /// }); /// static B: Lazy = Lazy::new(|ruby| { /// let class = ruby.define_class("B", Example::class(ruby)).unwrap(); /// class.undef_default_alloc_func(); /// class /// }); /// match value { /// Self::A => ruby.get_inner(&A), /// Self::B => ruby.get_inner(&B), /// } /// } /// } /// # let _ = (Example::A, Example::B); /// ``` #[allow(unused_variables)] fn class_for(ruby: &Ruby, value: &Self) -> RClass { Self::class(ruby) } } impl TryConvert for &T where T: TypedData, { fn try_convert(val: Value) -> Result { let handle = Ruby::get_with(val); unsafe { RTypedData::from_value(val) .ok_or_else(|| { Error::new( handle.exception_type_error(), format!( "no implicit conversion of {} into {}", val.classname(), T::class(&handle) ), ) })? .get_unconstrained() } } } impl IntoValue for T where T: TypedData, { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle.wrap(self).into_value_with(handle) } } /// A Ruby Object wrapping a Rust type `T`. /// /// This is a Value pointer to a RTypedData struct, Ruby’s internal /// representation of objects that wrap foreign types. Unlike [`RTypedData`] it /// tracks the Rust type it should contains and errors early in [`TryConvert`] /// if types don't match. /// /// See the [`ReprValue`] and [`Object`] traits for additional methods /// available on this type. See [`Ruby`](Ruby#typed_dataobj) for methods to /// create a `typed_data::Obj`. #[repr(transparent)] pub struct Obj { inner: RTypedData, phantom: PhantomData, } impl Copy for Obj where T: TypedData {} impl Clone for Obj where T: TypedData, { fn clone(&self) -> Self { *self } } /// # `typed_data::Obj` /// /// Functions to wrap Rust data in a Ruby object. /// /// See also [`RTypedData`](Ruby#rtypeddata) and the [`typed_data::Obj`](Obj) /// type. impl Ruby { /// Wrap the Rust type `T` in a Ruby object. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// #[magnus::wrap(class = "Point")] /// struct Point { /// x: isize, /// y: isize, /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let point_class = ruby.define_class("Point", ruby.class_object())?; /// /// let value = ruby.obj_wrap(Point { x: 4, y: 2 }); /// assert!(value.is_kind_of(point_class)); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap(); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` pub fn obj_wrap(&self, data: T) -> Obj where T: TypedData, { Obj { inner: self.wrap(data), phantom: PhantomData, } } /// Wrap the Rust type `T` in a Ruby object that is an instance of the /// given `class`. /// /// See also [`TypedData::class_for`]. /// /// # Panics /// /// Panics if `class` is not a subclass of `::class()`. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// #[magnus::wrap(class = "Point")] /// struct Point { /// x: isize, /// y: isize, /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let point_class = ruby.define_class("Point", ruby.class_object())?; /// let point_sub_class = ruby.define_class("SubPoint", point_class)?; /// /// let value = ruby.obj_wrap_as(Point { x: 4, y: 2 }, point_sub_class); /// assert!(value.is_kind_of(point_sub_class)); /// assert!(value.is_kind_of(point_class)); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap(); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` /// /// Allowing a wrapped type to be subclassed from Ruby: /// /// (note, in this example `Point` does not have and does not call the /// `initialize` method, subclasses would need to override the class `new` /// method rather than `initialize`) /// /// ``` /// use magnus::{function, method, prelude::*, typed_data, Error, RClass, Ruby, Value}; /// /// #[magnus::wrap(class = "Point")] /// struct Point { /// x: isize, /// y: isize, /// } /// /// impl Point { /// fn new(ruby: &Ruby, class: RClass, x: isize, y: isize) -> typed_data::Obj { /// ruby.obj_wrap_as(Self { x, y }, class) /// } /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let point_class = ruby.define_class("Point", ruby.class_object())?; /// point_class.define_singleton_method("new", method!(Point::new, 2))?; /// point_class /// .define_singleton_method("inherited", function!(RClass::undef_default_alloc_func, 1))?; /// /// let value: Value = ruby.eval( /// r#" /// class SubPoint < Point /// end /// SubPoint.new(4, 2) /// "#, /// )?; /// /// assert!(value.is_kind_of(ruby.class_object().const_get::<_, RClass>("SubPoint")?)); /// assert!(value.is_kind_of(point_class)); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap(); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` pub fn obj_wrap_as(&self, data: T, class: RClass) -> Obj where T: TypedData, { Obj { inner: self.wrap_as(data, class), phantom: PhantomData, } } } impl Obj where T: TypedData, { /// Wrap the Rust type `T` in a Ruby object. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::obj_wrap`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{class, define_class, prelude::*, typed_data}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// #[magnus::wrap(class = "Point")] /// struct Point { /// x: isize, /// y: isize, /// } /// /// let point_class = define_class("Point", class::object()).unwrap(); /// /// let value = typed_data::Obj::wrap(Point { x: 4, y: 2 }); /// assert!(value.is_kind_of(point_class)); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::obj_wrap` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn wrap(data: T) -> Self { get_ruby!().obj_wrap(data) } /// Wrap the Rust type `T` in a Ruby object that is an instance of the /// given `class`. /// /// See also [`TypedData::class_for`]. /// /// # Panics /// /// Panics if `class` is not a subclass of `::class()`, or /// if called from a non-Ruby thread. See [`Ruby::obj_wrap_as`] for a /// version that can not be called from a non-Ruby thread, so will not /// panic for that reason. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{class, define_class, prelude::*, typed_data}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// #[magnus::wrap(class = "Point")] /// struct Point { /// x: isize, /// y: isize, /// } /// /// let point_class = define_class("Point", class::object()).unwrap(); /// let point_sub_class = define_class("SubPoint", point_class).unwrap(); /// /// let value = typed_data::Obj::wrap_as(Point { x: 4, y: 2 }, point_sub_class); /// assert!(value.is_kind_of(point_sub_class)); /// assert!(value.is_kind_of(point_class)); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` /// /// Allowing a wrapped type to be subclassed from Ruby: /// /// (note, in this example `Point` does not have and does not call /// the `initialize` method, subclasses would need to override the class /// `new` method rather than `initialize`) /// /// ``` /// # #![allow(deprecated)] /// use magnus::{ /// class, define_class, eval, function, method, prelude::*, typed_data, RClass, Value, /// }; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// #[magnus::wrap(class = "Point")] /// struct Point { /// x: isize, /// y: isize, /// } /// /// impl Point { /// fn new(class: RClass, x: isize, y: isize) -> typed_data::Obj { /// typed_data::Obj::wrap_as(Self { x, y }, class) /// } /// } /// let point_class = define_class("Point", class::object()).unwrap(); /// point_class /// .define_singleton_method("new", method!(Point::new, 2)) /// .unwrap(); /// point_class /// .define_singleton_method("inherited", function!(RClass::undef_default_alloc_func, 1)) /// .unwrap(); /// /// let value: Value = eval( /// r#" /// class SubPoint < Point /// end /// SubPoint.new(4, 2) /// "#, /// ) /// .unwrap(); /// /// assert!(value.is_kind_of(class::object().const_get::<_, RClass>("SubPoint").unwrap())); /// assert!(value.is_kind_of(point_class)); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::obj_wrap_as` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn wrap_as(data: T, class: RClass) -> Self { get_ruby!().obj_wrap_as(data, class) } } impl Deref for Obj where T: TypedData, { type Target = T; /// Dereference to the Rust type wrapped in the Ruby object `self`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// #[magnus::wrap(class = "Point")] /// #[derive(Debug, PartialEq, Eq)] /// struct Point { /// x: isize, /// y: isize, /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_class("Point", ruby.class_object())?; /// let value = ruby.obj_wrap(Point { x: 4, y: 2 }); /// /// assert_eq!(&*value, &Point { x: 4, y: 2 }); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn deref(&self) -> &Self::Target { self.inner.get().unwrap() } } impl fmt::Display for Obj where T: TypedData, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for Obj where T: TypedData, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for Obj where T: TypedData, { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { self.inner.into_value_with(handle) } } impl From> for RTypedData where T: TypedData, { fn from(val: Obj) -> Self { val.inner } } impl Object for Obj where T: TypedData {} unsafe impl private::ReprValue for Obj where T: TypedData {} impl ReprValue for Obj where T: TypedData {} impl TryConvert for Obj where T: TypedData, { fn try_convert(val: Value) -> Result { let handle = Ruby::get_with(val); let inner = RTypedData::from_value(val).ok_or_else(|| { Error::new( handle.exception_type_error(), format!( "no implicit conversion of {} into {}", unsafe { val.classname() }, T::class(&handle) ), ) })?; // check it really does contain a T inner.get::()?; Ok(Self { inner, phantom: PhantomData, }) } } /// Trait for a Ruby-compatible `#hash` method. /// /// Automatically implemented for any type implementing [`std::hash::Hash`]. /// /// See also [`Dup`], [`Inspect`], [`IsEql`], and [`typed_data::Cmp`](Cmp). /// /// # Examples /// /// ``` /// use std::hash::Hasher; /// /// use magnus::{ /// function, gc, method, prelude::*, typed_data, value::Opaque, DataTypeFunctions, Error, /// Ruby, TypedData, Value, /// }; /// /// #[derive(TypedData)] /// #[magnus(class = "Pair", free_immediately, mark)] /// struct Pair { /// #[magnus(opaque_attr_reader)] /// a: Opaque, /// #[magnus(opaque_attr_reader)] /// b: Opaque, /// } /// /// impl Pair { /// fn new(a: Value, b: Value) -> Self { /// Self { /// a: a.into(), /// b: b.into(), /// } /// } /// } /// /// impl DataTypeFunctions for Pair { /// fn mark(&self, marker: &gc::Marker) { /// marker.mark(self.a); /// marker.mark(self.b); /// } /// } /// /// impl std::hash::Hash for Pair { /// fn hash(&self, state: &mut H) { /// state.write_i64( /// self.a() /// .hash() /// .expect("#hash should not fail") /// .to_i64() /// .expect("#hash result guaranteed to be <= i64"), /// ); /// state.write_i64( /// self.b() /// .hash() /// .expect("#hash should not fail") /// .to_i64() /// .expect("#hash result guaranteed to be <= i64"), /// ); /// } /// } /// /// impl PartialEq for Pair { /// fn eq(&self, other: &Self) -> bool { /// self.a().eql(other.a()).unwrap_or(false) && self.b().eql(other.b()).unwrap_or(false) /// } /// } /// /// impl Eq for Pair {} /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let class = ruby.define_class("Pair", ruby.class_object())?; /// class.define_singleton_method("new", function!(Pair::new, 2))?; /// class.define_method("hash", method!(::hash, 0))?; /// class.define_method("eql?", method!(::is_eql, 1))?; /// /// let a = Pair::new( /// ruby.str_new("foo").as_value(), /// ruby.integer_from_i64(1).as_value(), /// ); /// let hash = ruby.hash_new(); /// hash.aset(a, "test value")?; /// /// let b = Pair::new( /// ruby.str_new("foo").as_value(), /// ruby.integer_from_i64(1).as_value(), /// ); /// assert_eq!("test value", hash.fetch::<_, String>(b)?); /// /// let c = Pair::new( /// ruby.str_new("bar").as_value(), /// ruby.integer_from_i64(2).as_value(), /// ); /// assert!(hash.get(c).is_none()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub trait Hash { // Docs at trait level. #![allow(missing_docs)] fn hash(&self) -> i64; } impl Hash for T where T: std::hash::Hash, { fn hash(&self) -> i64 { let mut hasher = DefaultHasher::new(); std::hash::Hash::hash(self, &mut hasher); // Ensure the Rust usize hash converts nicely to Ruby's expected range // if we return usize it'd truncate to 0 for anything negative. hasher.finish() as i64 } } /// Trait for a Ruby-compatible `#eql?` method. /// /// Automatically implemented for any type implementing [`Eq`] and /// [`TryConvert`]. /// /// See also [`Dup`], [`Inspect`], [`typed_data::Cmp`](Cmp), and /// [`typed_data::Hash`](Hash). /// /// # Examples /// /// ``` /// use std::hash::Hasher; /// /// use magnus::{ /// function, gc, method, prelude::*, typed_data, value::Opaque, DataTypeFunctions, Error, /// Ruby, TypedData, Value, /// }; /// /// #[derive(TypedData)] /// #[magnus(class = "Pair", free_immediately, mark)] /// struct Pair { /// #[magnus(opaque_attr_reader)] /// a: Opaque, /// #[magnus(opaque_attr_reader)] /// b: Opaque, /// } /// /// impl Pair { /// fn new(a: Value, b: Value) -> Self { /// Self { /// a: a.into(), /// b: b.into(), /// } /// } /// } /// /// impl DataTypeFunctions for Pair { /// fn mark(&self, marker: &gc::Marker) { /// marker.mark(self.a); /// marker.mark(self.b); /// } /// } /// /// impl std::hash::Hash for Pair { /// fn hash(&self, state: &mut H) { /// state.write_i64( /// self.a() /// .hash() /// .expect("#hash should not fail") /// .to_i64() /// .expect("#hash result guaranteed to be <= i64"), /// ); /// state.write_i64( /// self.b() /// .hash() /// .expect("#hash should not fail") /// .to_i64() /// .expect("#hash result guaranteed to be <= i64"), /// ); /// } /// } /// /// impl PartialEq for Pair { /// fn eq(&self, other: &Self) -> bool { /// self.a().eql(other.a()).unwrap_or(false) && self.b().eql(other.b()).unwrap_or(false) /// } /// } /// /// impl Eq for Pair {} /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let class = ruby.define_class("Pair", ruby.class_object())?; /// class.define_singleton_method("new", function!(Pair::new, 2))?; /// class.define_method("hash", method!(::hash, 0))?; /// class.define_method("eql?", method!(::is_eql, 1))?; /// /// let a = Pair::new( /// ruby.str_new("foo").as_value(), /// ruby.integer_from_i64(1).as_value(), /// ); /// let hash = ruby.hash_new(); /// hash.aset(a, "test value")?; /// /// let b = Pair::new( /// ruby.str_new("foo").as_value(), /// ruby.integer_from_i64(1).as_value(), /// ); /// assert_eq!("test value", hash.fetch::<_, String>(b)?); /// /// let c = Pair::new( /// ruby.str_new("bar").as_value(), /// ruby.integer_from_i64(2).as_value(), /// ); /// assert!(hash.get(c).is_none()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub trait IsEql { // Docs at trait level. #![allow(missing_docs)] fn is_eql(&self, other: Value) -> bool; } impl<'a, T> IsEql for T where T: Eq + 'a, &'a T: TryConvert, { fn is_eql(&self, other: Value) -> bool { <&'a T>::try_convert(other) .map(|o| self == o) .unwrap_or(false) } } /// Trait for a Ruby-compatible `#<=>` method. /// /// Automatically implemented for any type implementing [`PartialOrd`] and /// [`TryConvert`]. /// /// See also [`Dup`], [`Inspect`], [`IsEql`] and [`typed_data::Hash`](Hash). /// /// # Examples /// /// ``` /// use std::cmp::Ordering; /// /// use magnus::{ /// function, gc, method, prelude::*, rb_assert, typed_data, value::Opaque, DataTypeFunctions, /// Error, Module, Ruby, TypedData, Value, /// }; /// /// #[derive(TypedData)] /// #[magnus(class = "Pair", free_immediately, mark)] /// struct Pair { /// #[magnus(opaque_attr_reader)] /// a: Opaque, /// #[magnus(opaque_attr_reader)] /// b: Opaque, /// } /// /// impl Pair { /// fn new(a: Value, b: Value) -> Self { /// Self { /// a: a.into(), /// b: b.into(), /// } /// } /// } /// /// impl DataTypeFunctions for Pair { /// fn mark(&self, marker: &gc::Marker) { /// marker.mark(self.a); /// marker.mark(self.b); /// } /// } /// /// impl PartialEq for Pair { /// fn eq(&self, other: &Self) -> bool { /// self.a().eql(other.a()).unwrap_or(false) && self.b().eql(other.b()).unwrap_or(false) /// } /// } /// /// impl PartialOrd for Pair { /// fn partial_cmp(&self, other: &Self) -> Option { /// let a = self /// .a() /// .funcall("<=>", (other.a(),)) /// .ok() /// .map(|o: i64| o.cmp(&0))?; /// match a { /// Ordering::Less | Ordering::Greater => Some(a), /// Ordering::Equal => self /// .b() /// .funcall("<=>", (other.b(),)) /// .ok() /// .map(|o: i64| o.cmp(&0)), /// } /// } /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let class = ruby.define_class("Pair", ruby.class_object())?; /// class.define_singleton_method("new", function!(Pair::new, 2))?; /// class.define_method("<=>", method!(::cmp, 1))?; /// class.include_module(ruby.module_comparable())?; /// /// let a = Pair::new( /// ruby.str_new("foo").as_value(), /// ruby.integer_from_i64(1).as_value(), /// ); /// let b = Pair::new( /// ruby.str_new("foo").as_value(), /// ruby.integer_from_i64(2).as_value(), /// ); /// rb_assert!(ruby, "a < b", a, b); /// /// let b = Pair::new( /// ruby.str_new("foo").as_value(), /// ruby.integer_from_i64(2).as_value(), /// ); /// let c = Pair::new( /// ruby.str_new("bar").as_value(), /// ruby.integer_from_i64(1).as_value(), /// ); /// rb_assert!(ruby, "b > c", b, c); /// /// let a = Pair::new( /// ruby.str_new("foo").as_value(), /// ruby.integer_from_i64(1).as_value(), /// ); /// let b = Pair::new( /// ruby.str_new("foo").as_value(), /// ruby.integer_from_i64(2).as_value(), /// ); /// rb_assert!(ruby, "(a <=> b) == -1", a, b); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub trait Cmp { // Docs at trait level. #![allow(missing_docs)] fn cmp(&self, other: Value) -> Option; } impl<'a, T> Cmp for T where T: PartialOrd + 'a, &'a T: TryConvert, { fn cmp(&self, other: Value) -> Option { <&'a T>::try_convert(other) .ok() .and_then(|o| self.partial_cmp(o)) .map(|o| o as i64) } } /// Trait for a Ruby-compatible `#inspect` method. /// /// Automatically implemented for any type implementing [`Debug`]. /// /// See also [`Dup`], [`IsEql`], [`typed_data::Cmp`](Cmp), and /// [`typed_data::Hash`](Hash). /// /// # Examples /// /// ``` /// use std::fmt; /// /// use magnus::{ /// function, gc, method, prelude::*, rb_assert, typed_data, value::Opaque, DataTypeFunctions, /// Error, Ruby, TypedData, Value, /// }; /// /// #[derive(TypedData)] /// #[magnus(class = "Pair", free_immediately, mark)] /// struct Pair { /// #[magnus(opaque_attr_reader)] /// a: Opaque, /// #[magnus(opaque_attr_reader)] /// b: Opaque, /// } /// /// impl Pair { /// fn new(a: Value, b: Value) -> Self { /// Self { /// a: a.into(), /// b: b.into(), /// } /// } /// } /// /// impl DataTypeFunctions for Pair { /// fn mark(&self, marker: &gc::Marker) { /// marker.mark(self.a); /// marker.mark(self.b); /// } /// } /// /// impl fmt::Debug for Pair { /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { /// f.debug_struct("Pair") /// .field("a", &self.a()) /// .field("b", &self.b()) /// .finish() /// } /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let class = ruby.define_class("Pair", ruby.class_object())?; /// class.define_singleton_method("new", function!(Pair::new, 2))?; /// class.define_method( /// "inspect", /// method!(::inspect, 0), /// )?; /// /// let pair = Pair::new( /// ruby.str_new("foo").as_value(), /// ruby.integer_from_i64(1).as_value(), /// ); /// rb_assert!(ruby, r#"pair.inspect == "Pair { a: \"foo\", b: 1 }""#, pair); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub trait Inspect { // Docs at trait level. #![allow(missing_docs)] fn inspect(&self) -> String; } impl Inspect for T where T: fmt::Debug, { fn inspect(&self) -> String { format!("{:?}", self) } } /// Trait for a Ruby-compatible `#dup` and `#clone` methods. /// /// Automatically implemented for any type implementing [`Clone`]. /// /// See also [`Inspect`], [`IsEql`], [`typed_data::Cmp`](Cmp), and /// [`typed_data::Hash`](Hash). /// /// # Examples /// /// ``` /// use magnus::{ /// function, gc, method, prelude::*, rb_assert, typed_data, value::Opaque, DataTypeFunctions, /// Error, Ruby, TypedData, Value, /// }; /// /// #[derive(TypedData, Clone)] /// #[magnus(class = "Pair", free_immediately, mark)] /// struct Pair { /// a: Opaque, /// b: Opaque, /// } /// /// impl Pair { /// fn new(a: Value, b: Value) -> Self { /// Self { /// a: a.into(), /// b: b.into(), /// } /// } /// } /// /// impl DataTypeFunctions for Pair { /// fn mark(&self, marker: &gc::Marker) { /// marker.mark(self.a); /// marker.mark(self.b); /// } /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let class = ruby.define_class("Pair", ruby.class_object())?; /// class.define_singleton_method("new", function!(Pair::new, 2))?; /// class.define_method("dup", method!(::dup, 0))?; /// class.define_method("clone", method!(::clone, -1))?; /// /// let a = Pair::new( /// ruby.str_new("foo").as_value(), /// ruby.integer_from_i64(1).as_value(), /// ); /// rb_assert!(ruby, "b = a.dup; a.object_id != b.object_id", a); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub trait Dup: Sized { // Docs at trait level. #![allow(missing_docs)] fn dup(&self) -> Self; fn clone(rbself: Obj, args: &[Value]) -> Result, Error>; } impl Dup for T where T: Clone + TypedData, { fn dup(&self) -> Self { self.clone() } fn clone(rbself: Obj, args: &[Value]) -> Result, Error> { let args = scan_args::<(), (), (), (), _, ()>(args)?; let kwargs = get_kwargs::<_, (), (Option>,), ()>(args.keywords, &[], &["freeze"])?; let (freeze,) = kwargs.optional; let freeze = freeze.flatten(); let clone = Ruby::get_with(rbself).obj_wrap((*rbself).clone()); let class_clone = unsafe { rb_singleton_class_clone(rbself.as_rb_value()) }; unsafe { rb_obj_reveal(clone.as_rb_value(), class_clone) }; unsafe { rb_singleton_class_attached(class_clone, clone.as_rb_value()) }; match freeze { Some(true) => clone.freeze(), None if rbself.is_frozen() => clone.freeze(), _ => (), } Ok(clone) } } magnus-0.7.1/src/value/flonum.rs000064400000000000000000000133131046102023000146750ustar 00000000000000use std::fmt; use rb_sys::{rb_float_new_in_heap, VALUE}; use crate::{ into_value::IntoValue, numeric::Numeric, try_convert::TryConvertOwned, value::{ private::{self, ReprValue as _}, NonZeroValue, ReprValue, }, Error, Float, RFloat, Ruby, TryConvert, Value, }; /// # `Flonum` /// /// Functions that can be used to create instances of Ruby's lower precision /// floating point representation. /// /// See also the [`Flonum`] type. impl Ruby { /// Create a new `Flonum` from a `f64.` /// /// Returns `Ok(Flonum)` if `n` can be represented as a `Flonum`, otherwise /// returns `Err(RFloat)`. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let f = ruby.flonum_from_f64(1.7272337110188893e-77).unwrap(); /// rb_assert!(ruby, "f == 1.7272337110188893e-77", f); /// /// // representable as a Float, but Flonum does not have enough precision /// assert!(ruby.flonum_from_f64(1.7272337110188890e-77).is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn flonum_from_f64(&self, n: f64) -> Result { Flonum::from_f64_impl(n) .ok_or_else(|| unsafe { RFloat::from_rb_value_unchecked(rb_float_new_in_heap(n)) }) } } /// A Value known to be a flonum, Ruby's internal representation of lower /// precision floating point numbers. /// /// See also [`Float`] and [`RFloat`]. /// /// See the [`ReprValue`] trait for additional methods available on this type. /// See [`Ruby`](Ruby#flonum) for methods to create a `Flonum`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct Flonum(NonZeroValue); impl Flonum { /// Return `Some(Flonum)` if `val` is a `Flonum`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, Flonum}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(Flonum::from_value(eval("1.7272337110188893e-77").unwrap()).is_some()); /// // representable as a Float, but Flonum does not have enough precision /// assert!(Flonum::from_value(eval("1.7272337110188890e-77").unwrap()).is_none()); /// // not a Flonum /// assert!(Flonum::from_value(eval("1").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { val.is_flonum() .then(|| Self(NonZeroValue::new_unchecked(val))) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } #[inline] pub(crate) fn from_f64_impl(d: f64) -> Option { let v = d.to_bits(); let bits = v >> 60 & 0x7; if v != 0x3000000000000000 && bits.wrapping_sub(3) & !0x01 == 0 { return Some(unsafe { Self::from_rb_value_unchecked(v.rotate_left(3) & !0x01 | 0x02) }); } else if v == 0 { return Some(unsafe { Self::from_rb_value_unchecked(0x8000000000000002) }); } None } /// Create a new `Flonum` from a `f64.` /// /// Returns `Ok(Flonum)` if `n` can be represented as a `Flonum`, otherwise /// returns `Err(RFloat)`. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::flonum_from_f64`] /// for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{rb_assert, Flonum}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let f = Flonum::from_f64(1.7272337110188893e-77).unwrap(); /// rb_assert!("f == 1.7272337110188893e-77", f); /// /// // representable as a Float, but Flonum does not have enough precision /// assert!(Flonum::from_f64(1.7272337110188890e-77).is_err()); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::flonum_from_f64` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn from_f64(n: f64) -> Result { get_ruby!().flonum_from_f64(n) } /// Convert `self` to a `f64`. /// /// # Examples /// /// ``` /// use magnus::{eval, Flonum}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let f: Flonum = eval("2.0").unwrap(); /// assert_eq!(f.to_f64(), 2.0); /// ``` #[inline] pub fn to_f64(self) -> f64 { let v = self.as_rb_value(); if v != 0x8000000000000002 { let b63 = v >> 63; let v = (2_u64.wrapping_sub(b63) | v & !0x03).rotate_right(3); return f64::from_bits(v); } 0.0 } } impl fmt::Display for Flonum { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for Flonum { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for Flonum { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl Numeric for Flonum {} unsafe impl private::ReprValue for Flonum {} impl ReprValue for Flonum {} impl TryConvert for Flonum { fn try_convert(val: Value) -> Result { let float = Float::try_convert(val)?; if let Some(flonum) = Flonum::from_value(float.as_value()) { Ok(flonum) } else { Err(Error::new( Ruby::get_with(val).exception_range_error(), "float out of range for flonum", )) } } } unsafe impl TryConvertOwned for Flonum {} magnus-0.7.1/src/value.rs000064400000000000000000003203141046102023000133770ustar 00000000000000//! Types for working with Ruby's VALUE type, representing all objects, and //! 'immediate' values such as Fixnum. #[cfg(ruby_use_flonum)] mod flonum; use std::{ borrow::{Borrow, Cow}, cell::UnsafeCell, ffi::CStr, fmt, hash::{Hash, Hasher}, marker::PhantomData, mem::transmute, num::NonZeroUsize, ops::{Deref, DerefMut}, os::raw::{c_char, c_int, c_long, c_ulong}, ptr, sync::Once, }; #[cfg(ruby_use_flonum)] pub use flonum::Flonum; use rb_sys::{ rb_any_to_s, rb_block_call_kw, rb_check_funcall_kw, rb_check_id, rb_check_id_cstr, rb_check_symbol_cstr, rb_enumeratorize_with_size_kw, rb_eql, rb_equal, rb_funcall_with_block_kw, rb_funcallv_kw, rb_funcallv_public_kw, rb_gc_register_address, rb_gc_unregister_address, rb_hash, rb_id2name, rb_id2sym, rb_inspect, rb_intern3, rb_ll2inum, rb_obj_as_string, rb_obj_classname, rb_obj_freeze, rb_obj_is_kind_of, rb_obj_respond_to, rb_sym2id, rb_ull2inum, ruby_fl_type, ruby_special_consts, ruby_value_type, RBasic, ID, VALUE, }; // These don't seem to appear consistently in bindgen output, not sure if they // aren't consistently defined in the headers or what. Lets just do it // ourselves. const RUBY_FIXNUM_MAX: c_ulong = (c_long::MAX / 2) as c_ulong; const RUBY_FIXNUM_MIN: c_long = c_long::MIN / 2; use crate::{ block::Proc, class::RClass, encoding::EncodingCapable, enumerator::Enumerator, error::{protect, Error}, gc, integer::{Integer, IntegerType}, into_value::{kw_splat, ArgList, IntoValue, IntoValueFromNative}, method::{Block, BlockReturn}, module::Module, numeric::Numeric, r_bignum::RBignum, r_string::RString, symbol::{IntoSymbol, Symbol}, try_convert::{TryConvert, TryConvertOwned}, Ruby, }; /// Ruby's `VALUE` type, which can represent any Ruby object. /// /// Methods for `Value` are implemented on the [`ReprValue`] trait, which is /// also implemented for all Ruby types. #[derive(Clone, Copy)] #[repr(transparent)] pub struct Value(VALUE, PhantomData<*mut RBasic>); impl Value { #[inline] pub(crate) const fn new(val: VALUE) -> Self { Self(val, PhantomData) } #[inline] pub(crate) const fn as_rb_value(self) -> VALUE { self.0 } } impl fmt::Display for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for Value { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self } } impl IntoValue for i8 { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle.integer_from_i64(self as i64).into_value_with(handle) } } unsafe impl IntoValueFromNative for i8 {} impl IntoValue for i16 { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle.integer_from_i64(self as i64).into_value_with(handle) } } unsafe impl IntoValueFromNative for i16 {} impl IntoValue for i32 { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle.integer_from_i64(self as i64).into_value_with(handle) } } unsafe impl IntoValueFromNative for i32 {} impl IntoValue for i64 { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle.integer_from_i64(self).into_value_with(handle) } } unsafe impl IntoValueFromNative for i64 {} impl IntoValue for isize { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle.integer_from_i64(self as i64).into_value_with(handle) } } unsafe impl IntoValueFromNative for isize {} impl IntoValue for u8 { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle.integer_from_u64(self as u64).into_value_with(handle) } } unsafe impl IntoValueFromNative for u8 {} impl IntoValue for u16 { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle.integer_from_u64(self as u64).into_value_with(handle) } } unsafe impl IntoValueFromNative for u16 {} impl IntoValue for u32 { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle.integer_from_u64(self as u64).into_value_with(handle) } } unsafe impl IntoValueFromNative for u32 {} impl IntoValue for u64 { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle.integer_from_u64(self).into_value_with(handle) } } unsafe impl IntoValueFromNative for u64 {} impl IntoValue for usize { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle.integer_from_u64(self as u64).into_value_with(handle) } } unsafe impl IntoValueFromNative for usize {} impl IntoValue for f32 { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle.float_from_f64(self as f64).into_value_with(handle) } } unsafe impl IntoValueFromNative for f32 {} impl IntoValue for f64 { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle.float_from_f64(self).into_value_with(handle) } } unsafe impl IntoValueFromNative for f64 {} impl TryConvert for Value { #[inline] fn try_convert(val: Value) -> Result { Ok(val) } } /// A wrapper to make a Ruby type [`Send`] + [`Sync`]. /// /// Ruby types are not [`Send`] or [`Sync`] as they provide a way to call /// Ruby's APIs, which it is not safe to do from a non-Ruby thread. /// /// Ruby types are safe to send between Ruby threads, but Rust's trait system /// currently can not model this detail. /// /// To resolve this, the `Opaque` type makes a Ruby type [`Send`] + [`Sync`] /// by removing the ability to do anything with it, making it impossible to /// call Ruby's API on non-Ruby threads. /// /// An `Opaque` can be unwrapped to `T` with [`Ruby::get_inner`], /// as it is only possible to instantiate a [`Ruby`] on a Ruby thread. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, value::Opaque, Ruby}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let ruby = Ruby::get().unwrap(); /// let opaque_str = Opaque::from(ruby.str_new("example")); /// /// // send to another Ruby thread /// /// let ruby = Ruby::get().unwrap(); // errors on non-Ruby thread /// let str = ruby.get_inner(opaque_str); /// rb_assert!(ruby, r#"str == "example""#, str); /// ``` #[derive(Clone, Copy)] #[repr(transparent)] pub struct Opaque(T); // implementation detail for opaque_attr_accessor proc macro attribute #[doc(hidden)] pub trait OpaqueVal { type Val: ReprValue; } impl OpaqueVal for Opaque where T: ReprValue, { type Val = T; } impl From for Opaque where T: ReprValue, { #[inline] fn from(val: T) -> Self { Self(val) } } impl IntoValue for Opaque where T: IntoValue, { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { self.0.into_value_with(handle) } } /// Helper trait for [`Ruby::get_inner`]. /// /// This trait allows for [`Ruby::get_inner`] to get the inner value of both /// [`Opaque`] and [`Lazy`]. pub trait InnerValue { /// The specific Ruby value type. type Value: ReprValue; /// Get the inner value from `self`. /// /// `ruby` acts as a token proving this is called from a Ruby thread and /// thus it is safe to return the inner value. /// /// # Examples /// /// ``` /// use magnus::{ /// rb_assert, /// value::{InnerValue, Opaque}, /// Ruby, /// }; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let ruby = Ruby::get().unwrap(); /// let opaque_str = Opaque::from(ruby.str_new("example")); /// /// // send to another Ruby thread /// /// let ruby = Ruby::get().unwrap(); // errors on non-Ruby thread /// let str = opaque_str.get_inner_with(&ruby); /// rb_assert!(ruby, r#"str == "example""#, str); /// ``` /// /// ``` /// use magnus::{ /// rb_assert, /// value::{InnerValue, Lazy}, /// RString, Ruby, /// }; /// /// static STATIC_STR: Lazy = Lazy::new(|ruby| ruby.str_new("example")); /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let ruby = Ruby::get().unwrap(); // errors if Ruby not initialised /// let str = STATIC_STR.get_inner_with(&ruby); /// rb_assert!(ruby, r#"str == "example""#, str); /// ``` fn get_inner_with(self, ruby: &Ruby) -> Self::Value; } impl InnerValue for Opaque where T: ReprValue, { type Value = T; #[inline] fn get_inner_with(self, _: &Ruby) -> Self::Value { self.0 } } /// Helper trait for [`Ruby::get_inner_ref`]. /// /// This trait allows for [`Ruby::get_inner_ref`] to get a reference to the /// inner value of both [`Opaque`] and [`Lazy`]. pub trait InnerRef { /// The specific Ruby value type. type Value: ReprValue; /// Get a reference to the inner value from `self`. /// /// `ruby` acts as a token proving this is called from a Ruby thread and /// thus it is safe to access the inner value. fn get_inner_ref_with<'a>(&'a self, ruby: &Ruby) -> &'a Self::Value; } impl InnerRef for Opaque where T: ReprValue, { type Value = T; #[inline] fn get_inner_ref_with<'a>(&'a self, _: &Ruby) -> &'a Self::Value { &self.0 } } /// # Extracting values from `Opaque`/`Lazy` /// /// Magnus has a number of container types where it is only safe to access the /// inner Ruby value when you can provide a `Ruby` handle. The functions here /// provide a unified api to access those container types. /// /// See also the [`Opaque`] and [`Lazy`] types. impl Ruby { /// Get the inner value from `wrapper`. /// /// `self` acts as a token proving this is called from a Ruby thread and /// thus it is safe to return the inner value. See [`Opaque`] and [`Lazy`]. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, value::Opaque, Ruby}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let ruby = Ruby::get().unwrap(); /// let opaque_str = Opaque::from(ruby.str_new("example")); /// /// // send to another Ruby thread /// /// let ruby = Ruby::get().unwrap(); // errors on non-Ruby thread /// let str = ruby.get_inner(opaque_str); /// rb_assert!(ruby, r#"str == "example""#, str); /// ``` /// /// ``` /// use magnus::{rb_assert, value::Lazy, RString, Ruby}; /// /// static STATIC_STR: Lazy = Lazy::new(|ruby| ruby.str_new("example")); /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let ruby = Ruby::get().unwrap(); // errors if Ruby not initialised /// let str = ruby.get_inner(&STATIC_STR); /// rb_assert!(ruby, r#"str == "example""#, str); /// ``` #[inline] pub fn get_inner(&self, wrapper: impl InnerValue) -> T where T: ReprValue, { wrapper.get_inner_with(self) } /// Get a reference to the inner value of `wrapper`. /// /// `self` acts as a token proving this is called from a Ruby thread and /// thus it is safe to access the inner value. See [`Opaque`] and [`Lazy`]. #[inline] pub fn get_inner_ref<'a, T>(&self, wrapper: &'a impl InnerRef) -> &'a T where T: ReprValue, { wrapper.get_inner_ref_with(self) } } unsafe impl Send for Opaque {} unsafe impl Sync for Opaque {} /// Lazily initialise a Ruby value so it can be assigned to a `static`. /// /// Ruby types require the Ruby VM to be started before they can be initialise, /// so can't be assigned to `static`s. They also can't safely be used from /// non-Ruby threads, which a `static` Ruby value would allow. /// /// Lazy allows assigning a Ruby value to a static by lazily initialising it /// on first use, and by requiring a value of [`Ruby`] to access the inner /// value, which it is only possible to obtain once the Ruby VM has started and /// on on a Ruby thread. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, value::Lazy, RString, Ruby}; /// /// static STATIC_STR: Lazy = Lazy::new(|ruby| ruby.str_new("example")); /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let ruby = Ruby::get().unwrap(); // errors if Ruby not initialised /// let str = ruby.get_inner(&STATIC_STR); /// rb_assert!(ruby, r#"str == "example""#, str); /// ``` pub struct Lazy { init: Once, mark: bool, func: fn(&Ruby) -> T, value: UnsafeCell, } impl Lazy where T: ReprValue, { /// Create a new `Lazy`. /// /// This function can be called in a `const` context. `func` is evaluated /// when the `Lazy` is first accessed (see [`Ruby::get_inner`]). If /// multiple threads attempt first access at the same time `func` may be /// called more than once, but all threads will recieve the same value. /// /// This function assumes the `Lazy` will be assinged to a `static`, so /// marks the inner Ruby value with Ruby's garbage collector to never be /// garbage collected. See [`Lazy::new_without_mark`] if this is not wanted. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, value::Lazy, RString, Ruby}; /// /// static STATIC_STR: Lazy = Lazy::new(|ruby| ruby.str_new("example")); /// /// # let _cleanup = unsafe { magnus::embed::init() }; /// let ruby = Ruby::get().unwrap(); /// let str = ruby.get_inner(&STATIC_STR); /// rb_assert!(ruby, r#"str == "example""#, str); /// ``` pub const fn new(func: fn(&Ruby) -> T) -> Self { Self { init: Once::new(), mark: true, func, value: UnsafeCell::new(QNIL.0.get()), } } /// Create a new `Lazy` without protecting the inner value from Ruby's /// garbage collector. /// /// # Safety /// /// The `Lazy` returned from this function must be kept on the stack, or /// the inner value otherwise protected from Ruby's garbage collector. pub const unsafe fn new_without_mark(func: fn(&Ruby) -> T) -> Self { Self { init: Once::new(), mark: false, func, value: UnsafeCell::new(QNIL.0.get()), } } /// Force evaluation of a `Lazy`. /// /// This can be used in, for example, your [`init`](macro@crate::init) /// function to force initialisation of the `Lazy`. /// /// # Examples /// /// ``` /// use magnus::{value::Lazy, RString, Ruby}; /// /// static STATIC_STR: Lazy = Lazy::new(|ruby| ruby.str_new("example")); /// /// #[magnus::init] /// fn init(ruby: &Ruby) { /// Lazy::force(&STATIC_STR, ruby); /// /// assert!(Lazy::try_get_inner(&STATIC_STR).is_some()); /// } /// # let ruby = unsafe { magnus::embed::init() }; /// # init(&ruby); /// ``` #[inline] pub fn force(this: &Self, handle: &Ruby) { handle.get_inner(this); } /// Get the inner value as an [`Opaque`](Opaque) from a `Lazy`, if /// it has already been initialised. /// /// This function will not call Ruby and will not initialise the inner /// value. If the `Lazy` has not yet been initialised, returns `None`. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, value::Lazy, RString, Ruby}; /// /// static STATIC_STR: Lazy = Lazy::new(|ruby| ruby.str_new("example")); /// /// # let _cleanup = unsafe { magnus::embed::init() }; /// assert!(Lazy::try_get_inner(&STATIC_STR).is_none()); /// /// let ruby = Ruby::get().unwrap(); /// let str = ruby.get_inner(&STATIC_STR); /// rb_assert!(ruby, r#"str == "example""#, str); /// /// assert!(Lazy::try_get_inner(&STATIC_STR).is_some()); /// ``` pub fn try_get_inner(this: &Self) -> Option> { unsafe { this.init .is_completed() .then(|| T::from_value_unchecked(*this.value.get()).into()) } } } unsafe impl Sync for Lazy {} impl InnerValue for &Lazy where T: ReprValue, { type Value = T; #[inline] fn get_inner_with(self, ruby: &Ruby) -> Self::Value { *self.get_inner_ref_with(ruby) } } impl InnerRef for Lazy where T: ReprValue, { type Value = T; fn get_inner_ref_with<'a>(&'a self, ruby: &Ruby) -> &'a Self::Value { unsafe { if !self.init.is_completed() { let value = (self.func)(ruby); self.init.call_once(|| { if self.mark { gc::register_mark_object(value); } *self.value.get() = value.as_value(); }); } T::ref_from_ref_value_unchecked(&*self.value.get()) } } } pub(crate) mod private { use super::*; use crate::value::ReprValue as _; /// Marker trait for types that have the same representation as [`Value`]. /// /// Types that are `ReprValue` can be safely transmuted to Value. /// /// # Safety /// /// This trait should only be implemented for types that a guaranteed to /// have the same layout as [`Value`] and have come from the Ruby VM. pub unsafe trait ReprValue: Copy { /// Convert `val` to a `Self`. /// /// # Safety /// /// This should only be used when `val` is known to uphold all the // invariants of `Self`. It is recommended not to use this method. #[inline] unsafe fn from_value_unchecked(val: Value) -> Self { *(&val as *const Value as *const Self) } #[inline] unsafe fn ref_from_ref_value_unchecked(val: &Value) -> &Self { &*(val as *const Value as *const Self) } #[inline] fn copy_as_value(self) -> Value { // This trait is only ever implemented for things with the same // representation as Value unsafe { *(&self as *const Self as *const Value) } } #[inline] fn as_value_ref(&self) -> &Value { // This trait is only ever implemented for things with the same // representation as Value unsafe { &*(self as *const Self as *const Value) } } #[inline] fn as_rb_value(self) -> VALUE { self.copy_as_value().0 } #[inline] unsafe fn r_basic_unchecked(self) -> ptr::NonNull { #[cfg(debug_assertions)] if self.is_immediate() { panic!("attempting to access immediate value as pointer"); } ptr::NonNull::new_unchecked(self.copy_as_value().0 as *mut RBasic) } /// Returns whether `self` is an 'immediate' value. /// /// 'immediate' values are encoded directly into the `Value` and /// require no additional lookup. They will never be garbage /// collected. /// /// non-immediate values are pointers to other memory holding the data /// for the object. #[inline] fn is_immediate(self) -> bool { let value_p = self.as_rb_value(); let immediate_p = value_p & ruby_special_consts::RUBY_IMMEDIATE_MASK as VALUE != 0; let test = value_p & !(ruby_special_consts::RUBY_Qnil as VALUE) != 0; immediate_p || !test // special_const_p } #[inline] fn r_basic(self) -> Option> { unsafe { (!self.is_immediate()).then(|| self.r_basic_unchecked()) } } #[inline] fn is_false(self) -> bool { self.as_rb_value() == ruby_special_consts::RUBY_Qfalse as VALUE } #[inline] fn is_true(self) -> bool { self.as_rb_value() == ruby_special_consts::RUBY_Qtrue as VALUE } #[inline] fn is_undef(self) -> bool { self.as_rb_value() == ruby_special_consts::RUBY_Qundef as VALUE } #[inline] fn is_fixnum(self) -> bool { self.as_rb_value() & ruby_special_consts::RUBY_FIXNUM_FLAG as VALUE != 0 } #[inline] fn is_static_symbol(self) -> bool { const MASK: usize = !(usize::MAX << ruby_special_consts::RUBY_SPECIAL_SHIFT as usize); self.as_rb_value() as usize & MASK == ruby_special_consts::RUBY_SYMBOL_FLAG as usize } #[inline] fn is_flonum(self) -> bool { self.as_rb_value() & ruby_special_consts::RUBY_FLONUM_MASK as VALUE == ruby_special_consts::RUBY_FLONUM_FLAG as VALUE } // derefs a raw pointer that under GC compaction may be outside the // process's memory space if the Value has been allowed to get GC'd #[inline] fn rb_type(self) -> ruby_value_type { match self.r_basic() { Some(r_basic) => { unsafe { let ret = r_basic.as_ref().flags & (ruby_value_type::RUBY_T_MASK as VALUE); // this bit is safe, ruby_value_type is #[repr(u32)], the flags // value set by Ruby, and Ruby promises that flags masked like // this will always be a valid entry in this enum std::mem::transmute(ret as u32) } } None => { if self.is_false() { ruby_value_type::RUBY_T_FALSE } else if self.copy_as_value().is_nil() { ruby_value_type::RUBY_T_NIL } else if self.is_true() { ruby_value_type::RUBY_T_TRUE } else if self.is_undef() { ruby_value_type::RUBY_T_UNDEF } else if self.is_fixnum() { ruby_value_type::RUBY_T_FIXNUM } else if self.is_static_symbol() { ruby_value_type::RUBY_T_SYMBOL } else if self.is_flonum() { ruby_value_type::RUBY_T_FLOAT } else { unreachable!() } } } } /// Convert `self` to a string. If an error is encountered returns a /// generic string (usually the object's class name). /// /// # Safety /// /// This may return a direct view of memory owned and managed by Ruby. /// Ruby may modify or free the memory backing the returned /// str, the caller must ensure this does not happen. #[allow(clippy::wrong_self_convention)] unsafe fn to_s_infallible(&self) -> Cow { match self.as_value_ref().to_s() { Ok(v) => v, Err(_) => Cow::Owned( RString::from_rb_value_unchecked(rb_any_to_s(self.as_rb_value())) .to_string_lossy() .into_owned(), ), } } } } use private::ReprValue as _; /// Marker trait for types that have the same representation as [`Value`]. /// /// Types that are `ReprValue` can be safely transmuted to Value. pub trait ReprValue: private::ReprValue { /// Return `self` as a [`Value`]. #[inline] fn as_value(self) -> Value { // This trait is only ever implemented for things with the same // representation as Value unsafe { *(&self as *const Self as *const Value) } } /// Returns whether `self` is Ruby's `nil` value. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ruby.eval::("nil")?.is_nil()); /// assert!(!ruby.eval::("Object.new")?.is_nil()); /// assert!(!ruby.eval::("0")?.is_nil()); /// assert!(!ruby.eval::("[]")?.is_nil()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] fn is_nil(self) -> bool { self.as_rb_value() == ruby_special_consts::RUBY_Qnil as VALUE } /// Checks for equality, delegating to the Ruby method `#==`. /// /// Ruby optimises this check if `self` and `other` are the same object /// or some built-in types, then calling the `#==` method will be skipped. /// /// Returns `Err` if `#==` raises. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = ruby.ary_from_vec(vec![1, 2, 3]); /// let b = ruby.ary_from_vec(vec![1, 2, 3]); /// let c = ruby.ary_from_vec(vec![4, 5, 6]); /// let d = ruby.integer_from_i64(1); /// assert!(a.equal(a)?); /// assert!(a.equal(b)?); /// assert!(!a.equal(c)?); /// assert!(!a.equal(d)?); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{eval, prelude::*, Error, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let (a, b): (Value, Value) = eval!( /// ruby, /// " /// class Example /// def ==(other) /// raise /// end /// end /// [Example.new, Example.new] /// " /// )?; /// /// assert!(a.equal(b).is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn equal(self, other: T) -> Result where T: ReprValue, { unsafe { protect(|| Value::new(rb_equal(self.as_rb_value(), other.as_rb_value()))) .map(Value::to_bool) } } /// Checks for equality, delegating to the Ruby method `#eql?`. /// /// See [`Value::equal`] for the equivalent of the `#==` method. /// /// Ruby optimises this check if `self` and `other` are the same object /// for some built-in types, then calling the `#==` method will be skipped. /// /// Returns `Err` if `#eql?` raises. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let a = ruby.ary_from_vec(vec![1, 2, 3]); /// let b = ruby.ary_from_vec(vec![1, 2, 3]); /// let c = ruby.ary_from_vec(vec![4, 5, 6]); /// let d = ruby.integer_from_i64(1); /// assert!(a.eql(a)?); /// assert!(a.eql(b)?); /// assert!(!a.eql(c)?); /// assert!(!a.eql(d)?); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{eval, prelude::*, Error, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let (a, b): (Value, Value) = eval!( /// ruby, /// " /// class Example /// def eql?(other) /// raise /// end /// end /// [Example.new, Example.new] /// " /// )?; /// /// assert!(a.eql(b).is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn eql(self, other: T) -> Result where T: ReprValue, { unsafe { protect(|| Value::new(rb_eql(self.as_rb_value(), other.as_rb_value()) as VALUE)) .map(Value::to_bool) } } /// Returns an integer non-uniquely identifying `self`. /// /// The return value is not stable between different Ruby processes. /// /// Ruby guarantees the return value will be in the range of a C `long`, /// this is usually equivalent to a `i64`, though will be `i32` on Windows. /// /// Ruby built-in classes will not error, but it is possible for badly /// behaving 3rd party classes (or collections such as `Array` containing /// them) to error in this function. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ruby /// .str_new("test") /// .hash()? /// .equal(ruby.str_new("test").hash()?)?); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn hash(self) -> Result { unsafe { protect(|| Integer::from_rb_value_unchecked(rb_hash(self.as_rb_value()))) } } /// Returns the class that `self` is an instance of. /// /// # Panics /// /// panics if self is `Qundef`. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert_eq!(ruby.eval::("true")?.class().inspect(), "TrueClass"); /// assert_eq!(ruby.eval::("[1,2,3]")?.class().inspect(), "Array"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn class(self) -> RClass { let handle = Ruby::get_with(self); unsafe { match self.r_basic() { Some(r_basic) => RClass::from_rb_value_unchecked(r_basic.as_ref().klass), None => { if self.is_false() { handle.class_false_class() } else if self.is_nil() { handle.class_nil_class() } else if self.is_true() { handle.class_true_class() } else if self.is_undef() { panic!("undef does not have a class") } else if self.is_fixnum() { handle.class_integer() } else if self.is_static_symbol() { handle.class_symbol() } else if self.is_flonum() { handle.class_float() } else { unreachable!() } } } } } /// Returns whether `self` is 'frozen'. /// /// Ruby prevents modifying frozen objects. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ruby.eval::(":foo")?.is_frozen()); /// assert!(ruby.eval::("42")?.is_frozen()); /// assert!(!ruby.eval::("[]")?.is_frozen()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn is_frozen(self) -> bool { match self.r_basic() { None => true, Some(r_basic) => unsafe { r_basic.as_ref().flags & ruby_fl_type::RUBY_FL_FREEZE as VALUE != 0 }, } } /// Returns an error if `self` is 'frozen'. /// /// Useful for checking if an object is frozen in a function that would /// modify it. /// /// # Examples /// ``` /// use magnus::{prelude::*, Error, Ruby, Value}; /// /// fn mutate(val: Value) -> Result<(), Error> { /// val.check_frozen()?; /// /// // ... /// Ok(()) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(mutate(ruby.eval("Object.new")?).is_ok()); /// assert!(mutate(ruby.eval(":foo")?).is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn check_frozen(self) -> Result<(), Error> { if self.is_frozen() { Err(Error::new( Ruby::get_with(self).exception_frozen_error(), format!("can't modify frozen {}", unsafe { self.classname() }), )) } else { Ok(()) } } /// Mark `self` as frozen. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_new(); /// assert!(!ary.is_frozen()); /// ary.freeze(); /// assert!(ary.is_frozen()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn freeze(self) { unsafe { rb_obj_freeze(self.as_rb_value()) }; } /// Convert `self` to a `bool`, following Ruby's rules of `false` and `nil` /// as boolean `false` and everything else boolean `true`. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(!ruby.eval::("false")?.to_bool()); /// assert!(!ruby.eval::("nil")?.to_bool()); /// /// assert!(ruby.eval::("true")?.to_bool()); /// assert!(ruby.eval::("0")?.to_bool()); /// assert!(ruby.eval::("[]")?.to_bool()); /// assert!(ruby.eval::(":foo")?.to_bool()); /// assert!(ruby.eval::("Object.new")?.to_bool()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] fn to_bool(self) -> bool { self.as_rb_value() & !(ruby_special_consts::RUBY_Qnil as VALUE) != 0 } /// Call the method named `method` on `self` with `args`. /// /// Returns `Ok(T)` if the method returns without error and the return /// value converts to a `T`, or returns `Err` if the method raises or the /// conversion fails. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, RArray, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let values = ruby.eval::(r#"["foo", 1, :bar]"#)?; /// let result: String = values.funcall("join", (" & ",))?; /// assert_eq!(result, "foo & 1 & bar"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// ``` /// use magnus::{eval, kwargs, prelude::*, Error, RObject, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let object: RObject = eval!( /// ruby, /// r#" /// class Adder /// def add(a, b, c:) /// a + b + c /// end /// end /// /// Adder.new /// "# /// )?; /// /// let result: i32 = object.funcall("add", (1, 2, kwargs!("c" => 3)))?; /// assert_eq!(result, 6); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn funcall(self, method: M, args: A) -> Result where M: IntoId, A: ArgList, T: TryConvert, { let handle = Ruby::get_with(self); let id = method.into_id_with(&handle); let kw_splat = kw_splat(&args); let args = args.into_arg_list_with(&handle); let slice = args.as_ref(); unsafe { protect(|| { Value::new(rb_funcallv_kw( self.as_rb_value(), id.as_rb_id(), slice.len() as c_int, slice.as_ptr() as *const VALUE, kw_splat as c_int, )) }) .and_then(TryConvert::try_convert) } } /// Call the public method named `method` on `self` with `args`. /// /// Returns `Ok(T)` if the method returns without error and the return /// value converts to a `T`, or returns `Err` if the method raises or the /// conversion fails. /// /// # Examples /// /// ``` /// use magnus::{eval, prelude::*, Error, RObject, Ruby, Symbol}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let object: RObject = eval!( /// ruby, /// r#" /// class Foo /// def bar /// :bar /// end /// /// private /// /// def baz /// :baz /// end /// end /// /// Foo.new /// "# /// )?; /// /// let result: Symbol = object.funcall_public("bar", ())?; /// assert_eq!(result.name()?, "bar"); /// /// let result: Result = object.funcall_public("baz", ()); /// assert!(result.is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn funcall_public(self, method: M, args: A) -> Result where M: IntoId, A: ArgList, T: TryConvert, { let handle = Ruby::get_with(self); let id = method.into_id_with(&handle); let kw_splat = kw_splat(&args); let args = args.into_arg_list_with(&handle); let slice = args.as_ref(); unsafe { protect(|| { Value::new(rb_funcallv_public_kw( self.as_rb_value(), id.as_rb_id(), slice.len() as c_int, slice.as_ptr() as *const VALUE, kw_splat as c_int, )) }) .and_then(TryConvert::try_convert) } } /// If `self` responds to the method named `method`, call it with `args`. /// /// Returns `Some(Ok(T))` if the method exists and returns without error, /// `None` if it does not exist, or `Some(Err)` if an exception was raised. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Integer, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let val = ruby.float_from_f64(1.23); /// let res: Integer = val.check_funcall("to_int", ()).unwrap()?; /// assert_eq!(res.to_i64()?, 1); /// /// let val = ruby.str_new("1.23"); /// let res: Option> = val.check_funcall("to_int", ()); /// assert!(res.is_none()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn check_funcall(self, method: M, args: A) -> Option> where M: IntoId, A: ArgList, T: TryConvert, { let handle = Ruby::get_with(self); let id = method.into_id_with(&handle); let kw_splat = kw_splat(&args); let args = args.into_arg_list_with(&handle); let slice = args.as_ref(); unsafe { let result = protect(|| { Value::new(rb_check_funcall_kw( self.as_rb_value(), id.as_rb_id(), slice.len() as c_int, slice.as_ptr() as *const VALUE, kw_splat as c_int, )) }); match result { Ok(v) if v.is_undef() => None, Ok(v) => Some(T::try_convert(v)), Err(e) => Some(Err(e)), } } } /// Call the method named `method` on `self` with `args` and `block`. /// /// Similar to [`funcall`](Value::funcall), but passes `block` as a Ruby /// block to the method. /// /// See also [`block_call`](Value::block_call). /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, RArray, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let values = ruby.eval::(r#"["foo", 1, :bar]"#)?; /// let block = ruby.proc_new(|_ruby, args, _block| args.first().unwrap().to_r_string()); /// let _: Value = values.funcall_with_block("map!", (), block)?; /// assert_eq!(values.to_vec::()?, vec!["foo", "1", "bar"]); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn funcall_with_block(self, method: M, args: A, block: Proc) -> Result where M: IntoId, A: ArgList, T: TryConvert, { let handle = Ruby::get_with(self); let id = method.into_id_with(&handle); let kw_splat = kw_splat(&args); let args = args.into_arg_list_with(&handle); let slice = args.as_ref(); unsafe { protect(|| { Value::new(rb_funcall_with_block_kw( self.as_rb_value(), id.as_rb_id(), slice.len() as c_int, slice.as_ptr() as *const VALUE, block.as_rb_value(), kw_splat as c_int, )) }) .and_then(TryConvert::try_convert) } } /// Call the method named `method` on `self` with `args` and `block`. /// /// Similar to [`funcall`](Value::funcall), but passes `block` as a Ruby /// block to the method. /// /// As `block` is a function pointer, only functions and closures that do /// not capture any variables are permitted. For more flexibility (at the /// cost of allocating) see [`Proc::from_fn`] and /// [`funcall_with_block`](Value::funcall_with_block). /// /// The function passed as `block` will receive values yielded to the block /// as a slice of [`Value`]s, plus `Some(Proc)` if the block itself was /// called with a block, or `None` otherwise. /// /// The `block` function may return any `R` or `Result` where `R` /// implements [`IntoValue`]. Returning `Err(Error)` will raise the error /// as a Ruby exception. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, RArray, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let values = ruby.eval::(r#"["foo", 1, :bar]"#)?; /// let _: Value = values.block_call("map!", (), |_ruby, args, _block| { /// args.first().unwrap().to_r_string() /// })?; /// assert_eq!(values.to_vec::()?, vec!["foo", "1", "bar"]); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn block_call( self, method: M, args: A, block: fn(&Ruby, &[Value], Option) -> R, ) -> Result where M: IntoId, A: ArgList, R: BlockReturn, T: TryConvert, { unsafe extern "C" fn call( _yielded_arg: VALUE, callback_arg: VALUE, argc: c_int, argv: *const VALUE, blockarg: VALUE, ) -> VALUE where R: BlockReturn, { let func = std::mem::transmute::) -> R>(callback_arg); func.call_handle_error(argc, argv as *const Value, Value::new(blockarg)) .as_rb_value() } let handle = Ruby::get_with(self); let id = method.into_id_with(&handle); let kw_splat = kw_splat(&args); let args = args.into_arg_list_with(&handle); let slice = args.as_ref(); let call_func = call:: as unsafe extern "C" fn(VALUE, VALUE, c_int, *const VALUE, VALUE) -> VALUE; protect(|| unsafe { #[allow(clippy::fn_to_numeric_cast)] Value::new(rb_block_call_kw( self.as_rb_value(), id.as_rb_id(), slice.len() as c_int, slice.as_ptr() as *const VALUE, Some(call_func), block as VALUE, kw_splat as c_int, )) }) .and_then(TryConvert::try_convert) } /// Check if `self` responds to the given Ruby method. /// /// The `include_private` agument controls whether `self`'s private methods /// are checked. If `false` they are not, if `true` they are. /// /// See also [`Value::check_funcall`]. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("example"); /// assert!(s.respond_to("to_str", false)?); /// assert!(!s.respond_to("puts", false)?); /// assert!(s.respond_to("puts", true)?); /// assert!(!s.respond_to("non_existant", false)?); /// assert!(!s.respond_to("non_existant", true)?); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn respond_to(self, method: M, include_private: bool) -> Result where M: IntoId, { let handle = Ruby::get_with(self); let id = method.into_id_with(&handle); let mut res = false; protect(|| { unsafe { res = rb_obj_respond_to(self.as_rb_value(), id.as_rb_id(), include_private as c_int) != 0 }; handle.qnil() })?; Ok(res) } /// Convert `self` to a Ruby `String`. /// /// If `self` is already a `String` is it wrapped as a `RString`, otherwise /// the Ruby `to_s` method is called. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let value = ruby.eval::("[]")?; /// assert!(value.to_r_string()?.is_kind_of(ruby.class_string())); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn to_r_string(self) -> Result { match RString::from_value(self.as_value()) { Some(v) => Ok(v), None => protect(|| unsafe { RString::from_rb_value_unchecked(rb_obj_as_string(self.as_rb_value())) }), } } /// Convert `self` to a Rust string. /// /// # Safety /// /// This may return a direct view of memory owned and managed by Ruby. Ruby /// may modify or free the memory backing the returned str, the caller must /// ensure this does not happen. /// /// This can be used safely by immediately calling /// [`into_owned`](Cow::into_owned) on the return value. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let value = ruby.qtrue(); /// // safe as we never give Ruby a chance to free the string. /// let s = unsafe { value.to_s() }?.into_owned(); /// assert_eq!(s, "true"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[allow(clippy::wrong_self_convention)] unsafe fn to_s(&self) -> Result, Error> { if let Some(s) = RString::ref_from_value(self.as_value_ref()) { if s.is_utf8_compatible_encoding() { return s.as_str().map(Cow::Borrowed); } else { return (*s).to_string().map(Cow::Owned); } } self.to_r_string() .and_then(|s| s.to_string().map(Cow::Owned)) } /// Convert `self` to its Ruby debug representation. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert_eq!(ruby.qnil().inspect(), "nil"); /// assert_eq!(ruby.to_symbol("foo").inspect(), ":foo"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn inspect(self) -> String { let handle = Ruby::get_with(self); unsafe { let s = protect(|| RString::from_rb_value_unchecked(rb_inspect(self.as_rb_value()))) .unwrap_or_else(|_| { RString::from_rb_value_unchecked(rb_any_to_s(self.as_rb_value())) }); s.conv_enc(handle.utf8_encoding()) .unwrap_or(s) .to_string_lossy() .into_owned() } } /// Return the name of `self`'s class. /// /// # Safety /// /// Ruby may modify or free the memory backing the returned str, the caller /// must ensure this does not happen. /// /// This can be used safely by immediately calling /// [`into_owned`](Cow::into_owned) on the return value. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let value = ruby.hash_new(); /// // safe as we never give Ruby a chance to free the string. /// let s = unsafe { value.classname() }.into_owned(); /// assert_eq!(s, "Hash"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` unsafe fn classname(&self) -> Cow { let ptr = rb_obj_classname(self.as_rb_value()); let cstr = CStr::from_ptr(ptr); cstr.to_string_lossy() } /// Returns whether or not `self` is an instance of `class`. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, Error, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let value = ruby.eval::("[]")?; /// assert!(value.is_kind_of(ruby.class_array())); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn is_kind_of(self, class: T) -> bool where T: ReprValue + Module, { unsafe { Value::new(rb_obj_is_kind_of(self.as_rb_value(), class.as_rb_value())).to_bool() } } /// Generate an [`Enumerator`] from `method` on `self`, passing `args` to /// `method`. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, r_string, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = r_string!("foo\\bar\\baz"); /// let mut i = 0; /// for line in s.enumeratorize("each_line", ("\\",)) { /// assert!(line?.is_kind_of(ruby.class_string())); /// i += 1; /// } /// assert_eq!(i, 3); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn enumeratorize(self, method: M, args: A) -> Enumerator where M: IntoSymbol, A: ArgList, { let handle = Ruby::get_with(self); let kw_splat = kw_splat(&args); let args = args.into_arg_list_with(&handle); let slice = args.as_ref(); unsafe { Enumerator::from_rb_value_unchecked(rb_enumeratorize_with_size_kw( self.as_rb_value(), method.into_symbol_with(&handle).as_rb_value(), slice.len() as c_int, slice.as_ptr() as *const VALUE, None, kw_splat as c_int, )) } } } unsafe impl private::ReprValue for Value {} impl ReprValue for Value {} #[derive(Clone, Copy, Eq, Hash, PartialEq)] #[repr(transparent)] pub(crate) struct NonZeroValue(NonZeroUsize, PhantomData>); impl NonZeroValue { #[inline] pub(crate) const unsafe fn new_unchecked(val: Value) -> Self { Self( NonZeroUsize::new_unchecked(val.as_rb_value() as usize), PhantomData, ) } #[inline] pub(crate) const fn get(self) -> Value { Value::new(self.0.get() as VALUE) } } /// Protects a Ruby Value from the garbage collector. /// /// See also [`gc::register_mark_object`] for a value that should be /// permanently excluded from garbage collection. pub struct BoxValue(Box); impl BoxValue where T: ReprValue, { /// Create a new `BoxValue`. /// /// # Examples /// /// ``` /// use magnus::{eval, value::BoxValue, Error, RString, Ruby, Value}; /// /// # #[inline(never)] /// fn box_value(ruby: &Ruby) -> BoxValue { /// BoxValue::new(ruby.str_new("foo")) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// # // get the Value in a different stack frame and copy it to a BoxValue /// # // test is invalid if this is done in this function. /// let boxed = box_value(ruby); /// /// # // make some garbage /// # ruby.eval::(r#"1024.times.map {|i| "test#{i}"}"#)?; /// // run garbage collector /// ruby.gc_start(); /// /// # // try and use value /// // boxed is still useable /// let result: String = eval!(ruby, r#"foo + "bar""#, foo = boxed)?; /// /// assert_eq!(result, "foobar"); /// /// # // didn't segfault? we passed! /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn new(val: T) -> Self { let mut boxed = Box::new(val); unsafe { rb_gc_register_address(boxed.as_mut() as *mut _ as *mut VALUE) }; Self(boxed) } } impl Drop for BoxValue { fn drop(&mut self) { unsafe { rb_gc_unregister_address(self.0.as_mut() as *mut _ as *mut VALUE); } } } impl AsRef for BoxValue { #[inline] fn as_ref(&self) -> &T { &self.0 } } impl AsMut for BoxValue { #[inline] fn as_mut(&mut self) -> &mut T { &mut self.0 } } impl Deref for BoxValue { type Target = T; #[inline] fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for BoxValue { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl fmt::Display for BoxValue where T: ReprValue, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.as_value().to_s_infallible() }) } } impl fmt::Debug for BoxValue where T: ReprValue, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_value().inspect()) } } impl IntoValue for BoxValue where T: ReprValue, { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.as_value() } } unsafe impl IntoValueFromNative for BoxValue where T: ReprValue {} /// # `false` /// /// Get Ruby's `false` value. /// /// See also the [`Qfalse`] type. impl Ruby { /// Returns Ruby's `false` value. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "val == false", val = ruby.qfalse()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn qfalse(&self) -> Qfalse { QFALSE } } /// Ruby's `false` value. /// /// See [`Ruby::qfalse`]/[`qfalse`] to obtain a value of this type. /// /// See the [`ReprValue`] trait for additional methods available on this type. #[derive(Clone, Copy)] #[repr(transparent)] pub struct Qfalse(Value); /// Ruby's `false` value. const QFALSE: Qfalse = Qfalse::new(); /// Returns Ruby's `false` value. /// /// This should optimise to a constant reference. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::qfalse`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{rb_assert, value::qfalse}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// rb_assert!("val == false", val = qfalse()); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::qfalse` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn qfalse() -> Qfalse { get_ruby!().qfalse() } impl Qfalse { /// Create a new `Qfalse`. #[inline] const fn new() -> Self { Qfalse(Value::new(ruby_special_consts::RUBY_Qfalse as VALUE)) } /// Return `Some(Qfalse)` if `val` is a `Qfalse`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, value::Qfalse}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(Qfalse::from_value(eval("false").unwrap()).is_some()); /// assert!(Qfalse::from_value(eval("0").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { val.is_false().then(Self::new) } } impl fmt::Display for Qfalse { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for Qfalse { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for Qfalse { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0 } } unsafe impl private::ReprValue for Qfalse {} impl ReprValue for Qfalse {} impl TryConvert for Qfalse { fn try_convert(val: Value) -> Result { Self::from_value(val).ok_or_else(|| { Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into FalseClass", unsafe { val.classname() },), ) }) } } unsafe impl TryConvertOwned for Qfalse {} /// # `nil` /// /// Get Ruby's `nil` value. /// /// See also the [`Qnil`] type. impl Ruby { /// Returns Ruby's `nil` value. /// /// This should optimise to a constant reference. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "val == nil", val = ruby.qnil()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn qnil(&self) -> Qnil { QNIL } } /// Ruby's `nil` value. /// /// See [`Ruby::qnil`]/[`qnil`] to obtain a value of this type. /// /// See the [`ReprValue`] trait for additional methods available on this type. #[derive(Clone, Copy)] #[repr(transparent)] pub struct Qnil(NonZeroValue); /// Ruby's `nil` value. const QNIL: Qnil = Qnil::new(); /// Returns Ruby's `nil` value. /// /// This should optimise to a constant reference. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::qnil`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{rb_assert, value::qnil}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// rb_assert!("val == nil", val = qnil()); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::qnil` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn qnil() -> Qnil { get_ruby!().qnil() } impl Qnil { /// Create a new `Qnil`. #[inline] const fn new() -> Self { unsafe { Self(NonZeroValue::new_unchecked(Value::new( ruby_special_consts::RUBY_Qnil as VALUE, ))) } } /// Return `Some(Qnil)` if `val` is a `Qnil`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, value::Qnil}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(Qnil::from_value(eval("nil").unwrap()).is_some()); /// assert!(Qnil::from_value(eval("0").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { val.is_nil().then(Self::new) } } impl fmt::Display for Qnil { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for Qnil { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for Qnil { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl IntoValue for () { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { handle.qnil().as_value() } } unsafe impl IntoValueFromNative for () {} impl IntoValue for Option where T: IntoValue, { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { match self { Some(t) => handle.into_value(t), None => handle.qnil().as_value(), } } } unsafe impl IntoValueFromNative for Option where T: IntoValueFromNative {} unsafe impl private::ReprValue for Qnil {} impl ReprValue for Qnil {} impl TryConvert for Qnil { fn try_convert(val: Value) -> Result { Self::from_value(val).ok_or_else(|| { Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into NilClass", unsafe { val.classname() },), ) }) } } unsafe impl TryConvertOwned for Qnil {} /// # `true` /// /// Get Ruby's `true` value. /// /// See also the [`Qtrue`] type. impl Ruby { /// Returns Ruby's `true` value. /// /// This should optimise to a constant reference. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "val == true", val = ruby.qtrue()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn qtrue(&self) -> Qtrue { QTRUE } } /// Ruby's `true` value. /// /// See [`Ruby::qtrue`]/[`qtrue`] to obtain a value of this type. /// /// See the [`ReprValue`] trait for additional methods available on this type. #[derive(Clone, Copy)] #[repr(transparent)] pub struct Qtrue(NonZeroValue); /// Ruby's `true` value. const QTRUE: Qtrue = Qtrue::new(); /// Returns Ruby's `true` value. /// /// This should optimise to a constant reference. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::qtrue`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{rb_assert, value::qtrue}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// rb_assert!("val == true", val = qtrue()); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::qtrue` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn qtrue() -> Qtrue { get_ruby!().qtrue() } impl Qtrue { /// Create a new `Qtrue`. #[inline] const fn new() -> Self { unsafe { Self(NonZeroValue::new_unchecked(Value::new( ruby_special_consts::RUBY_Qtrue as VALUE, ))) } } /// Return `Some(Qtrue)` if `val` is a `Qtrue`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, value::Qtrue}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(Qtrue::from_value(eval("true").unwrap()).is_some()); /// assert!(Qtrue::from_value(eval("1").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { val.is_true().then(Self::new) } } impl fmt::Display for Qtrue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for Qtrue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for Qtrue { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl IntoValue for bool { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { if self { handle.qtrue().as_value() } else { handle.qfalse().as_value() } } } unsafe impl IntoValueFromNative for bool {} unsafe impl private::ReprValue for Qtrue {} impl ReprValue for Qtrue {} impl TryConvert for Qtrue { fn try_convert(val: Value) -> Result { Self::from_value(val).ok_or_else(|| { Error::new( Ruby::get_with(val).exception_type_error(), format!("no implicit conversion of {} into TrueClass", unsafe { val.classname() },), ) }) } } unsafe impl TryConvertOwned for Qtrue {} /// A placeholder value that represents an undefined value. Not exposed to /// Ruby level code. /// /// See [`QUNDEF`] to obtain a value of this type. #[derive(Clone, Copy)] #[repr(transparent)] pub struct Qundef(NonZeroValue); /// A placeholder value that represents an undefined value. Not exposed to /// Ruby level code. pub const QUNDEF: Qundef = Qundef::new(); impl Qundef { /// Create a new `Qundef`. #[inline] const fn new() -> Self { unsafe { Self(NonZeroValue::new_unchecked(Value::new( ruby_special_consts::RUBY_Qundef as VALUE, ))) } } /// Return `Some(Qundef)` if `val` is a `Qundef`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, value::Qundef}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// // nil is not undef /// assert!(Qundef::from_value(eval("nil").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { val.is_undef().then(Self::new) } /// Return `self` as a [`Value`]. /// /// # Safety /// /// It is not a good idea to return this to Ruby code, bad things will /// happen. There are only a handful of places in Ruby's API where it is /// appropriate to pass a [`Value`] created from `Qundef` (hence this /// method, rather than implementing [`IntoValue`]). #[inline] pub unsafe fn as_value(self) -> Value { self.0.get() } } /// # `Fixnum` /// /// Functions that can be used to create instances of Ruby's small/fast integer /// representation. /// /// See also the [`Fixnum`] type. impl Ruby { /// Create a new `Fixnum` from an `i64.` /// /// Returns `Ok(Fixnum)` if `n` is in range for `Fixnum`, otherwise returns /// `Err(RBignum)`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ruby.fixnum_from_i64(0).is_ok()); /// // too big /// assert!(ruby.fixnum_from_i64(4611686018427387904).is_err()); /// assert!(ruby.fixnum_from_i64(-4611686018427387905).is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn fixnum_from_i64(&self, n: i64) -> Result { Fixnum::from_i64_impl(n) .ok_or_else(|| unsafe { RBignum::from_rb_value_unchecked(rb_ll2inum(n)) }) } /// Create a new `Fixnum` from a `u64.` /// /// Returns `Ok(Fixnum)` if `n` is in range for `Fixnum`, otherwise returns /// `Err(RBignum)`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ruby.fixnum_from_u64(0).is_ok()); /// // too big /// assert!(ruby.fixnum_from_u64(4611686018427387904).is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn fixnum_from_u64(&self, n: u64) -> Result { Fixnum::from_i64_impl(i64::try_from(n).unwrap_or(i64::MAX)) .ok_or_else(|| unsafe { RBignum::from_rb_value_unchecked(rb_ull2inum(n)) }) } } /// A Value known to be a fixnum, Ruby's internal representation of small /// integers. /// /// See also [`Integer`]. /// /// See the [`ReprValue`] trait for additional methods available on this type. /// See [`Ruby`](Ruby#fixnum) for methods to create a `Fixnum`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct Fixnum(NonZeroValue); impl Fixnum { /// Return `Some(Fixnum)` if `val` is a `Fixnum`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, Fixnum}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(Fixnum::from_value(eval("0").unwrap()).is_some()); /// // too big /// assert!(Fixnum::from_value(eval("9223372036854775807").unwrap()).is_none()); /// // not an int /// assert!(Fixnum::from_value(eval("1.23").unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { val.is_fixnum() .then(|| Self(NonZeroValue::new_unchecked(val))) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } #[inline] pub(crate) fn from_i64_impl(n: i64) -> Option { #[allow(clippy::useless_conversion)] // not useless when c_long != i64 (c_ulong::try_from(n) .map(|n| n < RUBY_FIXNUM_MAX + 1) .unwrap_or(false) && c_long::try_from(n) .map(|n| n >= RUBY_FIXNUM_MIN) .unwrap_or(false)) .then(|| unsafe { let x = transmute::<_, usize>(n as isize); Self::from_rb_value_unchecked(x.wrapping_add(x.wrapping_add(1)) as VALUE) }) } /// Create a new `Fixnum` from an `i64.` /// /// Returns `Ok(Fixnum)` if `n` is in range for `Fixnum`, otherwise returns /// `Err(RBignum)`. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::fixnum_from_i64`] /// for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::Fixnum; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(Fixnum::from_i64(0).is_ok()); /// // too big /// assert!(Fixnum::from_i64(4611686018427387904).is_err()); /// assert!(Fixnum::from_i64(-4611686018427387905).is_err()); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::fixnum_from_i64` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn from_i64(n: i64) -> Result { get_ruby!().fixnum_from_i64(n) } /// Create a new `Fixnum` from a `u64.` /// /// Returns `Ok(Fixnum)` if `n` is in range for `Fixnum`, otherwise returns /// `Err(RBignum)`. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::fixnum_from_u64`] /// for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::Fixnum; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(Fixnum::from_u64(0).is_ok()); /// // too big /// assert!(Fixnum::from_u64(4611686018427387904).is_err()); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::fixnum_from_u64` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn from_u64(n: u64) -> Result { get_ruby!().fixnum_from_u64(n) } fn is_negative(self) -> bool { unsafe { transmute::<_, isize>(self.0) < 0 } } /// Convert `self` to an `i8`. Returns `Err` if `self` is out of range for /// `i8`. /// /// # Examples /// /// ``` /// use magnus::{Error, Fixnum, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert_eq!(ruby.eval::("127")?.to_i8()?, 127); /// assert!(ruby.eval::("128")?.to_i8().is_err()); /// assert_eq!(ruby.eval::("-128")?.to_i8()?, -128); /// assert!(ruby.eval::("-129")?.to_i8().is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn to_i8(self) -> Result { let res = self.to_isize(); if res > i8::MAX as isize || res < i8::MIN as isize { return Err(Error::new( Ruby::get_with(self).exception_range_error(), "fixnum too big to convert into `i8`", )); } Ok(res as i8) } /// Convert `self` to an `i16`. Returns `Err` if `self` is out of range for /// `i16`. /// /// # Examples /// /// ``` /// use magnus::{Error, Fixnum, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert_eq!(ruby.eval::("32767")?.to_i16()?, 32767); /// assert!(ruby.eval::("32768")?.to_i16().is_err()); /// assert_eq!(ruby.eval::("-32768")?.to_i16()?, -32768); /// assert!(ruby.eval::("-32769")?.to_i16().is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn to_i16(self) -> Result { let res = self.to_isize(); if res > i16::MAX as isize || res < i16::MIN as isize { return Err(Error::new( Ruby::get_with(self).exception_range_error(), "fixnum too big to convert into `i16`", )); } Ok(res as i16) } /// Convert `self` to an `i32`. Returns `Err` if `self` is out of range for /// `i32`. /// /// # Examples /// /// ``` /// use magnus::{Error, Fixnum, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// # #[cfg(not(windows))] /// # { /// assert_eq!(ruby.eval::("2147483647")?.to_i32()?, 2147483647); /// assert!(ruby.eval::("2147483648")?.to_i32().is_err()); /// assert_eq!(ruby.eval::("-2147483648")?.to_i32()?, -2147483648); /// assert!(ruby.eval::("-2147483649")?.to_i32().is_err()); /// # } /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn to_i32(self) -> Result { let res = self.to_isize(); if res > i32::MAX as isize || res < i32::MIN as isize { return Err(Error::new( Ruby::get_with(self).exception_range_error(), "fixnum too big to convert into `i32`", )); } Ok(res as i32) } /// Convert `self` to an `i64`. This is infallible as `i64` can represent a /// larger range than `Fixnum`. /// /// # Examples /// /// ``` /// use magnus::{Error, Fixnum, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// # #[cfg(not(windows))] /// assert_eq!( /// ruby.eval::("4611686018427387903")?.to_i64(), /// 4611686018427387903 /// ); /// # #[cfg(not(windows))] /// assert_eq!( /// ruby.eval::("-4611686018427387904")?.to_i64(), /// -4611686018427387904 /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn to_i64(self) -> i64 { self.to_isize() as i64 } /// Convert `self` to an `isize`. Returns `Err` if `self` is out of range /// for `isize`. /// /// # Examples /// /// ``` /// use magnus::{Error, Fixnum, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// # #[cfg(not(windows))] /// assert_eq!( /// ruby.eval::("4611686018427387903")?.to_isize(), /// 4611686018427387903 /// ); /// # #[cfg(not(windows))] /// assert_eq!( /// ruby.eval::("-4611686018427387904")?.to_isize(), /// -4611686018427387904 /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn to_isize(self) -> isize { unsafe { transmute::<_, isize>(self) >> 1 } } /// Convert `self` to a `u8`. Returns `Err` if `self` is negative or out of /// range for `u8`. /// /// # Examples /// /// ``` /// use magnus::{Error, Fixnum, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert_eq!(ruby.eval::("255")?.to_u8()?, 255); /// assert!(ruby.eval::("256")?.to_u8().is_err()); /// assert!(ruby.eval::("-1")?.to_u8().is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn to_u8(self) -> Result { let handle = Ruby::get_with(self); if self.is_negative() { return Err(Error::new( handle.exception_range_error(), "can't convert negative integer to unsigned", )); } let res = self.to_isize(); if res > u8::MAX as isize { return Err(Error::new( handle.exception_range_error(), "fixnum too big to convert into `u8`", )); } Ok(res as u8) } /// Convert `self` to a `u16`. Returns `Err` if `self` is negative or out /// of range for `u16`. /// /// # Examples /// /// ``` /// use magnus::{Error, Fixnum, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert_eq!(ruby.eval::("65535")?.to_u16()?, 65535); /// assert!(ruby.eval::("65536")?.to_u16().is_err()); /// assert!(ruby.eval::("-1")?.to_u16().is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn to_u16(self) -> Result { let handle = Ruby::get_with(self); if self.is_negative() { return Err(Error::new( handle.exception_range_error(), "can't convert negative integer to unsigned", )); } let res = self.to_isize(); if res > u16::MAX as isize { return Err(Error::new( handle.exception_range_error(), "fixnum too big to convert into `u16`", )); } Ok(res as u16) } /// Convert `self` to a `u32`. Returns `Err` if `self` is negative or out /// of range for `u32`. /// /// # Examples /// /// ``` /// use magnus::{Error, Fixnum, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// # #[cfg(not(windows))] /// # { /// assert_eq!(ruby.eval::("4294967295")?.to_u32()?, 4294967295); /// assert!(ruby.eval::("4294967296")?.to_u32().is_err()); /// # } /// assert!(ruby.eval::("-1")?.to_u32().is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn to_u32(self) -> Result { let handle = Ruby::get_with(self); if self.is_negative() { return Err(Error::new( handle.exception_range_error(), "can't convert negative integer to unsigned", )); } let res = self.to_isize(); if res > u32::MAX as isize { return Err(Error::new( handle.exception_range_error(), "fixnum too big to convert into `u32`", )); } Ok(res as u32) } /// Convert `self` to a `u64`. Returns `Err` if `self` is negative. /// /// # Examples /// /// ``` /// use magnus::{Error, Fixnum, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// # #[cfg(not(windows))] /// assert_eq!( /// ruby.eval::("4611686018427387903")?.to_u64()?, /// 4611686018427387903 /// ); /// assert!(ruby.eval::("-1")?.to_u64().is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn to_u64(self) -> Result { if self.is_negative() { return Err(Error::new( Ruby::get_with(self).exception_range_error(), "can't convert negative integer to unsigned", )); } Ok(self.to_isize() as u64) } /// Convert `self` to a `usize`. Returns `Err` if `self` is negative or out /// of range for `usize`. /// /// # Examples /// /// ``` /// use magnus::{Error, Fixnum, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// # #[cfg(not(windows))] /// assert_eq!( /// ruby.eval::("4611686018427387903")?.to_usize()?, /// 4611686018427387903 /// ); /// assert!(ruby.eval::("-1")?.to_usize().is_err()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn to_usize(self) -> Result { if self.is_negative() { return Err(Error::new( Ruby::get_with(self).exception_range_error(), "can't convert negative integer to unsigned", )); } Ok(self.to_isize() as usize) } } impl fmt::Display for Fixnum { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for Fixnum { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for Fixnum { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } unsafe impl private::ReprValue for Fixnum {} impl Numeric for Fixnum {} impl ReprValue for Fixnum {} impl TryConvert for Fixnum { fn try_convert(val: Value) -> Result { match Integer::try_convert(val)?.integer_type() { IntegerType::Fixnum(fix) => Ok(fix), IntegerType::Bignum(_) => Err(Error::new( Ruby::get_with(val).exception_range_error(), "integer too big for fixnum", )), } } } unsafe impl TryConvertOwned for Fixnum {} /// # `StaticSymbol` /// /// Functions to create Ruby `Symbol`s that will never be garbage collected. /// /// See also the [`StaticSymbol`] type. impl Ruby { /// Create a new StaticSymbol. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let sym = ruby.sym_new("example"); /// rb_assert!(ruby, ":example == sym", sym); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn sym_new(&self, name: T) -> StaticSymbol where T: IntoId, { name.into_id_with(self).into() } /// Return the `StaticSymbol` for `name`, if one exists. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby, StaticSymbol}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ruby.check_symbol("example").is_none()); /// let _: StaticSymbol = ruby.eval(":example")?; /// assert!(ruby.check_symbol("example").is_some()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn check_symbol(&self, name: &str) -> Option { unsafe { let res = Value::new(rb_check_symbol_cstr( name.as_ptr() as *mut c_char, name.len() as c_long, self.utf8_encoding().as_ptr(), )); (!res.is_nil()).then(|| StaticSymbol::from_rb_value_unchecked(res.as_rb_value())) } } } /// A static Ruby symbol that will live for the life of the program and never /// be garbage collected. /// /// See also [`Symbol`]. /// /// See the [`ReprValue`] trait for additional methods available on this type. /// See [`Ruby`](Ruby#staticsymbol) for methods to create a `StaticSymbol`. #[derive(Clone, Copy, Eq, Hash, PartialEq)] #[repr(transparent)] pub struct StaticSymbol(NonZeroValue); impl StaticSymbol { /// Return `Some(StaticSymbol)` if `val` is a `StaticSymbol`, `None` /// otherwise. /// /// # Examples /// /// ``` /// use magnus::{eval, StaticSymbol}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(StaticSymbol::from_value(eval(":foo").unwrap()).is_some()); /// assert!(StaticSymbol::from_value(eval(r#""bar""#).unwrap()).is_none()); /// assert!(StaticSymbol::from_value(eval(r#""baz".to_sym"#).unwrap()).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { fn is_static_or_permanent_symbol(val: Value) -> bool { if val.is_static_symbol() { return true; } debug_assert_value!(val); if val.rb_type() != ruby_value_type::RUBY_T_SYMBOL { return false; } let mut p = val.as_rb_value(); unsafe { rb_check_id(&mut p as *mut _) != 0 } } unsafe { is_static_or_permanent_symbol(val).then(|| Self(NonZeroValue::new_unchecked(val))) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } /// Create a new StaticSymbol. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::sym_new`] for the /// non-panicking version. /// /// # Examples /// ``` /// # #![allow(deprecated)] /// use magnus::{rb_assert, StaticSymbol}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let sym = StaticSymbol::new("example"); /// rb_assert!(":example == sym", sym); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::sym_new` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn new(name: T) -> Self where T: IntoId, { get_ruby!().sym_new(name) } /// Return the `StaticSymbol` for `name`, if one exists. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::check_symbol`] for /// the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{eval, StaticSymbol}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(StaticSymbol::check("example").is_none()); /// let _: StaticSymbol = eval(":example").unwrap(); /// assert!(StaticSymbol::check("example").is_some()); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::check_symbol` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn check(name: &str) -> Option { get_ruby!().check_symbol(name) } /// Return the symbol as a static string reference. /// /// May error if the name is not valid utf-8. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let sym = ruby.sym_new("example"); /// assert_eq!(sym.name()?, "example"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn name(self) -> Result<&'static str, Error> { Id::from(self).name() } } impl Borrow for StaticSymbol { fn borrow(&self) -> &Symbol { unsafe { &*(self as *const Self as *const Symbol) } } } impl fmt::Display for StaticSymbol { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for StaticSymbol { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl EncodingCapable for StaticSymbol {} impl From for StaticSymbol { fn from(id: Id) -> Self { unsafe { Self::from_rb_value_unchecked(rb_id2sym(id.as_rb_id())) } } } impl IntoValue for StaticSymbol { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl PartialEq for StaticSymbol { #[inline] fn eq(&self, other: &Id) -> bool { self.into_id_with(&Ruby::get_with(*self)) == *other } } impl PartialEq for StaticSymbol { #[inline] fn eq(&self, other: &OpaqueId) -> bool { self.into_id_with(&Ruby::get_with(*self)).0 == other.0 } } impl PartialEq for StaticSymbol { /// # Panics /// /// Panics if the first call is from a non-Ruby thread. The `LazyId` will /// then be *poisoned* and all future use of it will panic. #[inline] fn eq(&self, other: &LazyId) -> bool { self.into_id_with(&Ruby::get_with(*self)).0 == other.0 } } impl PartialEq for StaticSymbol { #[inline] fn eq(&self, other: &Symbol) -> bool { other.as_static().map(|o| *self == o).unwrap_or(false) } } unsafe impl private::ReprValue for StaticSymbol {} impl ReprValue for StaticSymbol {} impl TryConvert for StaticSymbol { fn try_convert(val: Value) -> Result { Symbol::try_convert(val).map(|s| s.to_static()) } } unsafe impl TryConvertOwned for StaticSymbol {} /// # `Id` /// /// Functions to create Ruby's low-level `Symbol` representation. /// /// See also the [`Id`] type. impl Ruby { /// Create a new `Id` for `name`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let id = ruby.intern("example"); /// assert_eq!(id.name()?, "example"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn intern(&self, name: &str) -> Id { Id::from_rb_id(unsafe { rb_intern3( name.as_ptr() as *const c_char, name.len() as c_long, self.utf8_encoding().as_ptr(), ) }) } /// Return the `Id` for `name`, if one exists. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ruby.check_id("example").is_none()); /// ruby.intern("example"); /// assert!(ruby.check_id("example").is_some()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn check_id(&self, name: &str) -> Option { let res = unsafe { rb_check_id_cstr( name.as_ptr() as *mut c_char, name.len() as c_long, self.utf8_encoding().as_ptr(), ) }; (res != 0).then(|| Id::from_rb_id(res)) } } /// The internal value of a Ruby symbol. /// /// See [`Ruby`](Ruby#id) for methods to create an `Id`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[repr(transparent)] pub struct Id(ID, PhantomData<*mut u8>); impl Id { /// Create a new `Id` for `name`. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::intern`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::value::Id; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let id = Id::new("example"); /// assert_eq!(id.name().unwrap(), "example"); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::intern` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] pub fn new(name: T) -> Self where T: AsRef, { get_ruby!().intern(name.as_ref()) } #[inline] pub(crate) fn from_rb_id(id: ID) -> Self { Self(id, PhantomData) } #[inline] pub(crate) fn as_rb_id(self) -> ID { self.0 } /// Return the `Id` for `name`, if one exists. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::check_id`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{value::Id, StaticSymbol}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(Id::check("example").is_none()); /// StaticSymbol::new("example"); /// assert!(Id::check("example").is_some()); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::check_id` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn check(name: &str) -> Option { get_ruby!().check_id(name) } /// Return the symbol name associated with this Id as a static string /// reference. /// /// May error if the name is not valid utf-8. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let id = ruby.intern("example"); /// assert_eq!(id.name()?, "example"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn name(self) -> Result<&'static str, Error> { unsafe { let ptr = rb_id2name(self.as_rb_id()); let cstr = CStr::from_ptr(ptr); cstr.to_str().map_err(|e| { Error::new( Ruby::get_unchecked().exception_encoding_error(), e.to_string(), ) }) } } } impl Borrow for Id { fn borrow(&self) -> &OpaqueId { unsafe { &*(self as *const Self as *const OpaqueId) } } } /// Conversions from Rust types into [`Id`]. pub trait IntoId: Sized { /// Convert `self` into [`Id`]. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`IntoId::into_id_with`] /// for the non-panicking version. #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `IntoId::into_id_with` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] fn into_id(self) -> Id { self.into_id_with(&get_ruby!()) } /// Convert `self` into [`Id`]. /// /// # Safety /// /// This method should only be called from a Ruby thread. #[inline] unsafe fn into_id_unchecked(self) -> Id { self.into_id_with(&Ruby::get_unchecked()) } /// Convert `self` into [`Id`]. fn into_id_with(self, handle: &Ruby) -> Id; } impl IntoId for Id { #[inline] fn into_id_with(self, _: &Ruby) -> Id { self } } impl IntoId for &str { #[inline] fn into_id_with(self, handle: &Ruby) -> Id { handle.intern(self) } } impl IntoId for String { #[inline] fn into_id_with(self, handle: &Ruby) -> Id { self.as_str().into_id_with(handle) } } impl IntoId for StaticSymbol { #[inline] fn into_id_with(self, handle: &Ruby) -> Id { self.into_symbol_with(handle).into_id_with(handle) } } impl From for Id { #[inline] fn from(sym: StaticSymbol) -> Self { sym.into_id_with(&Ruby::get_with(sym)) } } impl IntoId for Symbol { #[inline] fn into_id_with(self, _: &Ruby) -> Id { if self.is_static_symbol() { Id::from_rb_id(self.as_rb_value() >> ruby_special_consts::RUBY_SPECIAL_SHIFT as VALUE) } else { Id::from_rb_id(unsafe { rb_sym2id(self.as_rb_value()) }) } } } impl IntoValue for Id { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { StaticSymbol::from(self).into_value_with(handle) } } impl From for Id { #[inline] fn from(sym: Symbol) -> Self { sym.into_id_with(&Ruby::get_with(sym)) } } impl PartialEq for Id { #[inline] fn eq(&self, other: &OpaqueId) -> bool { self.0 == other.0 } } impl PartialEq for Id { #[inline] fn eq(&self, other: &LazyId) -> bool { self.0 == other.0 } } impl PartialEq for Id { #[inline] fn eq(&self, other: &StaticSymbol) -> bool { *self == other.into_id_with(&Ruby::get_with(*other)) } } impl PartialEq for Id { #[inline] fn eq(&self, other: &Symbol) -> bool { other.as_static().map(|o| *self == o).unwrap_or(false) } } /// A wrapper to make a Ruby [`Id`] [`Send`] + [`Sync`]. /// /// [`Id`] is not [`Send`] or [`Sync`] as it provides a way to call some of /// Ruby's APIs, which are not safe to call from a non-Ruby thread. /// /// [`Id`] is safe to send between Ruby threads, but Rust's trait system /// currently can not model this detail. /// /// To resolve this, the `OpaqueId` type makes an [`Id`] [`Send`] + [`Sync`] /// by removing the ability use it with any Ruby APIs. /// /// [`IntoId`] and [`IntoSymbol`] can be used to convert an `OpaqueId` back /// into a type that can be used with Ruby's APIs. These traits can only be /// used from a Ruby thread. /// /// `OpaqueId` implements [`Eq`]/[`PartialEq`] and [`Hash`], so can be used as /// a key or id on non-Ruby threads, or in data structures that must be /// [`Send`] or [`Sync`]. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[repr(transparent)] pub struct OpaqueId(ID); impl From for OpaqueId { #[inline] fn from(id: Id) -> Self { Self(id.0) } } impl From for OpaqueId { #[inline] fn from(sym: StaticSymbol) -> Self { sym.into_id_with(&Ruby::get_with(sym)).into() } } impl From for OpaqueId { #[inline] fn from(sym: Symbol) -> Self { sym.into_id_with(&Ruby::get_with(sym)).into() } } impl IntoId for OpaqueId { #[inline] fn into_id_with(self, _: &Ruby) -> Id { Id::from_rb_id(self.0) } } impl IntoSymbol for OpaqueId { #[inline] fn into_symbol_with(self, handle: &Ruby) -> Symbol { self.into_id_with(handle).into_symbol_with(handle) } } impl IntoValue for OpaqueId { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { self.into_symbol_with(handle).into_value_with(handle) } } impl PartialEq for OpaqueId { #[inline] fn eq(&self, other: &Id) -> bool { self.0 == other.0 } } impl PartialEq for OpaqueId { #[inline] fn eq(&self, other: &LazyId) -> bool { *self == **other } } impl PartialEq for OpaqueId { #[inline] fn eq(&self, other: &StaticSymbol) -> bool { *self == other.into_id_with(&Ruby::get_with(*other)) } } impl PartialEq for OpaqueId { #[inline] fn eq(&self, other: &Symbol) -> bool { other.as_static().map(|o| *self == o).unwrap_or(false) } } /// An [`Id`] that can be assigned to a `static` and [`Deref`]s to [`OpaqueId`]. /// /// The underlying Ruby Symbol will be lazily initialised when the `LazyId` is /// first used. This initialisation must happen on a Ruby thread. If the first /// use is from a non-Ruby thread the `LazyId` will panic and then become /// *poisoned* and all future use of it will panic. pub struct LazyId { init: Once, inner: UnsafeCell, } union LazyIdInner { name: &'static str, value: OpaqueId, } impl LazyId { /// Create a new `LazyId`. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, value::LazyId, Error, Ruby}; /// /// static EXAMPLE: LazyId = LazyId::new("example"); /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// rb_assert!(ruby, "val == :example", val = *EXAMPLE); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub const fn new(name: &'static str) -> Self { Self { init: Once::new(), inner: UnsafeCell::new(LazyIdInner { name }), } } /// Force evaluation of a `LazyId`. /// /// This can be used in, for example, your [`init`](macro@crate::init) /// function to force initialisation of the `LazyId`, to ensure that use /// of the `LazyId` can't possibly panic. /// /// # Panics /// /// Panics if the `LazyId` is *poisoned*. See [`LazyId`]. /// /// # Examples /// /// ``` /// use magnus::{value::LazyId, Ruby}; /// /// static EXAMPLE: LazyId = LazyId::new("example"); /// /// #[magnus::init] /// fn init(ruby: &Ruby) { /// LazyId::force(&EXAMPLE, ruby); /// /// assert!(LazyId::try_get_inner(&EXAMPLE).is_some()); /// } /// # let ruby = unsafe { magnus::embed::init() }; /// # init(&ruby); /// ``` #[inline] pub fn force(this: &Self, handle: &Ruby) { Self::get_inner_with(this, handle); } /// Get a [`Id`] from a `LazyId`. /// /// # Panics /// /// Panics if the `LazyId` is *poisoned*. See [`LazyId`]. /// /// # Examples /// /// ``` /// use magnus::{value::LazyId, Error, Ruby}; /// /// static EXAMPLE: LazyId = LazyId::new("example"); /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ruby.intern("example") == LazyId::get_inner_with(&EXAMPLE, &ruby)); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn get_inner_with(this: &Self, handle: &Ruby) -> Id { unsafe { this.init.call_once(|| { let inner = this.inner.get(); (*inner).value = handle.intern((*inner).name).into(); }); (*this.inner.get()).value.into_id_with(handle) } } /// Get an [`OpaqueId`] from a `LazyId`, if it has already been evaluated. /// /// This function will not call Ruby and will not initialise the inner /// `OpaqueId`. If the `LazyId` has not yet been initialised, returns /// `None`. /// /// This function will not panic, if the `LazyId` is *poisoned* it will /// return `None`. /// /// # Examples /// /// ``` /// use magnus::{rb_assert, value::LazyId, Error, Ruby}; /// /// static EXAMPLE: LazyId = LazyId::new("example"); /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(LazyId::try_get_inner(&EXAMPLE).is_none()); /// /// rb_assert!(ruby, "val == :example", val = *EXAMPLE); /// /// assert!(LazyId::try_get_inner(&EXAMPLE).is_some()); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn try_get_inner(this: &Self) -> Option { unsafe { this.init.is_completed().then(|| (*this.inner.get()).value) } } } unsafe impl Send for LazyId {} unsafe impl Sync for LazyId {} impl fmt::Debug for LazyId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[allow(non_camel_case_types)] #[derive(Debug)] struct uninit(); f.debug_tuple("LazyId") .field( Self::try_get_inner(self) .as_ref() .map(|v| v as &dyn fmt::Debug) .unwrap_or(&uninit()), ) .finish() } } impl Deref for LazyId { type Target = OpaqueId; /// # Panics /// /// Panics if the first call is from a non-Ruby thread. This `LazyId` will /// then be *poisoned* and all future use of it will panic. fn deref(&self) -> &Self::Target { unsafe { self.init.call_once(|| { let inner = self.inner.get(); (*inner).value = Ruby::get().unwrap().intern((*inner).name).into(); }); &(*self.inner.get()).value } } } impl Hash for LazyId { /// # Panics /// /// Panics if the first call is from a non-Ruby thread. This `LazyId` will /// then be *poisoned* and all future use of it will panic. #[inline] fn hash(&self, state: &mut H) { self.deref().hash(state); } } impl PartialEq for LazyId { /// # Panics /// /// Panics if the first call is from a non-Ruby thread. This `LazyId` will /// then be *poisoned* and all future use of it will panic. #[inline] fn eq(&self, other: &Self) -> bool { self.deref() == other.deref() } } impl Eq for LazyId {} impl PartialEq for LazyId { /// # Panics /// /// Panics if the first call is from a non-Ruby thread. This `LazyId` will /// then be *poisoned* and all future use of it will panic. #[inline] fn eq(&self, other: &Id) -> bool { *self.deref() == *other } } impl PartialEq for LazyId { /// # Panics /// /// Panics if the first call is from a non-Ruby thread. This `LazyId` will /// then be *poisoned* and all future use of it will panic. #[inline] fn eq(&self, other: &StaticSymbol) -> bool { *self == other.into_id_with(&Ruby::get_with(*other)) } } impl PartialEq for LazyId { /// # Panics /// /// Panics if the first call is from a non-Ruby thread. This `LazyId` will /// then be *poisoned* and all future use of it will panic. #[inline] fn eq(&self, other: &Symbol) -> bool { other.as_static().map(|o| *self == o).unwrap_or(false) } } magnus-0.7.1/test000075500000000000000000000006451046102023000120350ustar 00000000000000#!/bin/sh RUBY_VERSIONS="2.7.8 3.0.6 3.1.4 3.2.2 3.3.0" ERRORS="" for VERSION in $RUBY_VERSIONS; do eval "$(rbenv init - sh)" rbenv shell $VERSION printf "testing Ruby $VERSION..." if OUTPUT=$(env RUBY="$(rbenv which ruby)" cargo +1.61 test --workspace 2>&1); then echo ✅ else ERRORS="$ERRORS\n\n\n$VERSION\n\n$OUTPUT" echo ❌ fi done if [ -n "$ERRORS" ]; then printf "$ERRORS" exit 1 fi magnus-0.7.1/tests/allocate_before_init.rs000064400000000000000000000007121046102023000167640ustar 00000000000000use magnus::eval; // this will fail if the allocator calls Ruby, like with rb-sys's // global-allocator feature #[test] fn can_allocate_before_ruby_init() { // allocate something let x: Vec = (0..256).collect(); // *then* init Ruby let ruby = unsafe { magnus::embed::init() }; // do something with allocated data to ensure it's not optimised away let res: i64 = eval!(ruby, "x.sum", x).unwrap(); assert_eq!(res, 32640); } magnus-0.7.1/tests/array_slice.rs000064400000000000000000000015271046102023000151350ustar 00000000000000use magnus::RArray; #[test] fn can_get_slice_from_r_attay() { let ruby = unsafe { magnus::embed::init() }; let ary: RArray = ruby.eval(r#"[1, nil, "foo"]"#).unwrap(); let slice = unsafe { ary.as_slice() }; assert_eq!(3, slice.len()); assert_eq!("1", format!("{:?}", slice[0])); assert_eq!("nil", format!("{:?}", slice[1])); assert_eq!(r#""foo""#, format!("{:?}", slice[2])); let ary: RArray = ruby .eval(r#"["bar", "baz", 42, [1, 2, 3], :test]"#) .unwrap(); let slice = unsafe { ary.as_slice() }; assert_eq!(5, slice.len()); assert_eq!(r#""bar""#, format!("{:?}", slice[0])); assert_eq!(r#""baz""#, format!("{:?}", slice[1])); assert_eq!("42", format!("{:?}", slice[2])); assert_eq!("[1, 2, 3]", format!("{:?}", slice[3])); assert_eq!(":test", format!("{:?}", slice[4])); } magnus-0.7.1/tests/block_call.rs000064400000000000000000000007551046102023000147270ustar 00000000000000use magnus::{prelude::*, Value}; #[test] fn it_can_call_method_with_block() { let ruby = unsafe { magnus::embed::init() }; let ary = ruby.ary_new_from_values(&[ ruby.into_value(1_i64), ruby.into_value(2_i64), ruby.into_value(3_i64), ]); let _: Value = ary .block_call("map!", (), |_, args, _| { i64::try_convert(args[0]).map(|i| i * 4) }) .unwrap(); assert_eq!(ary.to_vec::().unwrap(), vec![4, 8, 12]); } magnus-0.7.1/tests/bytes.rs000064400000000000000000000003661046102023000137660ustar 00000000000000#[test] fn it_converts_to_bytes() { use magnus::RString; let ruby = unsafe { magnus::embed::init() }; let s: RString = ruby.eval("[0,0,0].pack('c*')").unwrap(); assert_eq!(bytes::Bytes::from_static(&[0, 0, 0]), s.to_bytes()); } magnus-0.7.1/tests/call_proc.rs000064400000000000000000000003251046102023000145710ustar 00000000000000use magnus::block::Proc; #[test] fn it_calls_proc() { let ruby = unsafe { magnus::embed::init() }; let p: Proc = ruby.eval("Proc.new {|i| i + 1}").unwrap(); assert_eq!(43, p.call((42,)).unwrap()); } magnus-0.7.1/tests/classname.rs000064400000000000000000000003661046102023000146060ustar 00000000000000use magnus::{prelude::*, Value}; #[test] fn it_returns_the_class_name() { let ruby = unsafe { magnus::embed::init() }; unsafe { let val: Value = ruby.eval("42").unwrap(); assert_eq!("Integer", val.classname()); } } magnus-0.7.1/tests/clone.rs000064400000000000000000000037031046102023000137360ustar 00000000000000use magnus::{ eval, function, gc, method, prelude::*, typed_data, value::Opaque, DataTypeFunctions, TypedData, Value, }; #[derive(TypedData, Clone)] #[magnus(class = "Pair", free_immediately, mark)] struct Pair { #[magnus(opaque_attr_reader)] a: Opaque, #[magnus(opaque_attr_reader)] b: Opaque, } impl Pair { fn new(a: Value, b: Value) -> Self { Self { a: a.into(), b: b.into(), } } } impl DataTypeFunctions for Pair { fn mark(&self, marker: &gc::Marker) { marker.mark(self.a); marker.mark(self.b); } } #[test] fn it_matches_builtin_clone() { let ruby = unsafe { magnus::embed::init() }; let class = ruby.define_class("Pair", ruby.class_object()).unwrap(); class .define_singleton_method("new", function!(Pair::new, 2)) .unwrap(); class .define_method("dup", method!(::dup, 0)) .unwrap(); class .define_method("clone", method!(::clone, -1)) .unwrap(); let res: bool = eval!( ruby, r#" a = Pair.new("foo", 1) raise "shouldn't be frozen without freeze:" if a.clone.frozen? raise "shouldn't be frozen with freeze: nil" if a.clone(freeze: nil).frozen? raise "shouldn't be frozen with freeze: false" if a.clone(freeze: false).frozen? raise "should be frozen with freeze: true" unless a.clone(freeze: true).frozen? a.freeze raise "should be frozen without freeze:" unless a.clone.frozen? raise "should be frozen with freeze: nil" unless a.clone(freeze: nil).frozen? raise "shouldn't be frozen with #dup" if a.dup.frozen? b = Pair.new("bar", 2) def b.foobar "test" end raise "dup shouldn't copy singleton class" if b.dup.respond_to?(:foobar) b.clone.foobar == "test" "# ) .unwrap(); assert!(res); } magnus-0.7.1/tests/codepoints.rs000064400000000000000000000004121046102023000147770ustar 00000000000000#[test] fn it_stops_on_err() { let ruby = unsafe { magnus::embed::init() }; let s = ruby.str_new("foo"); s.cat([128]); // invalid s.cat("bar"); let count = unsafe { s.codepoints().count() }; // f, o, o, Err = 4 assert_eq!(count, 4); } magnus-0.7.1/tests/debug_exception.rs000064400000000000000000000021171046102023000160000ustar 00000000000000use magnus::error::ErrorType; #[test] fn it_includes_backtrace_in_debug() { let ruby = unsafe { magnus::embed::init() }; let err = ruby .eval::( r#" def foo raise "bang" end def bar foo end def baz bar end def qux baz end qux "#, ) .unwrap_err(); let ex = match err.error_type() { ErrorType::Exception(e) => e, _ => panic!(), }; let message = format!("{:#?}", ex); assert!(message.contains("RuntimeError: bang")); assert!(message.contains("eval:3:in")); assert!(message.contains("foo")); assert!(message.contains("eval:7:in")); assert!(message.contains("bar")); assert!(message.contains("eval:11:in")); assert!(message.contains("baz")); assert!(message.contains("eval:15:in")); assert!(message.contains("qux")); assert!(message.contains("eval:18:in")); assert!(message.contains("
")); } magnus-0.7.1/tests/encoding_capable.rs000064400000000000000000000014721046102023000160740ustar 00000000000000use magnus::prelude::*; #[test] fn it_works_across_type() { let ruby = unsafe { magnus::embed::init() }; assert!(ruby.str_new("example").enc_get() == ruby.utf8_encindex()); assert!(ruby.sym_new("example").enc_get() == ruby.usascii_encindex()); assert!(ruby.to_symbol("example").enc_get() == ruby.usascii_encindex()); // symbol upgrades to utf8 when required assert!(ruby.sym_new("🦀").enc_get() == ruby.utf8_encindex()); assert!(ruby.to_symbol("🦀").enc_get() == ruby.utf8_encindex()); let regexp = ruby.reg_new("example", Default::default()).unwrap(); assert!(regexp.enc_get() == ruby.usascii_encindex()); // regexp also upgrades to utf8 when needed let regexp = ruby.reg_new("🦀", Default::default()).unwrap(); assert!(regexp.enc_get() == ruby.utf8_encindex()); } magnus-0.7.1/tests/enumerator.rs000064400000000000000000000007361046102023000150220ustar 00000000000000use magnus::{prelude::*, RArray}; #[test] fn enumerator_impls_iterator() { let ruby = unsafe { magnus::embed::init() }; let a: RArray = ruby.eval("[1,2,3]").unwrap(); let mut e = a.enumeratorize("each", ()); assert_eq!(e.next().unwrap().and_then(i64::try_convert).unwrap(), 1); assert_eq!(e.next().unwrap().and_then(i64::try_convert).unwrap(), 2); assert_eq!(e.next().unwrap().and_then(i64::try_convert).unwrap(), 3); assert!(e.next().is_none()); } magnus-0.7.1/tests/enumeratorize.rs000064400000000000000000000010621046102023000155230ustar 00000000000000use magnus::{prelude::*, rb_assert, Value}; #[test] fn it_makes_an_enumerator() { let ruby = unsafe { magnus::embed::init() }; let val: Value = magnus::eval!( ruby, " class Test def each yield 1 yield 2 yield 3 end end Test.new " ) .unwrap(); let enumerator = val.enumeratorize("each", ()); rb_assert!(ruby, "enumerator.next == 1", enumerator); rb_assert!(ruby, "enumerator.next == 2", enumerator); rb_assert!(ruby, "enumerator.next == 3", enumerator); } magnus-0.7.1/tests/float_convert_from_value.rs000064400000000000000000000025111046102023000177160ustar 00000000000000use magnus::prelude::*; #[test] fn it_converts_floats_from_value() { let ruby = unsafe { magnus::embed::init() }; let val = ruby.eval::("1.0").unwrap(); let res = f64::try_convert(val); assert_eq!(res.unwrap(), 1.0); let val = ruby.eval::("1").unwrap(); let res = f64::try_convert(val); assert_eq!(res.unwrap(), 1.0); let val = ruby.eval::("1/2r").unwrap(); let res = f64::try_convert(val); assert_eq!(res.unwrap(), 0.5); let val = ruby.eval::("18446744073709551615").unwrap(); let res = f64::try_convert(val); assert_eq!(res.unwrap(), 18446744073709552000.0); let val = ruby.eval::(r#""1.0""#).unwrap(); let res = f64::try_convert(val); assert!(res.is_err()); let val = ruby.eval::("Object.new").unwrap(); let res = f64::try_convert(val); assert!(res.is_err()); let val = ruby.eval::("nil").unwrap(); let res = f64::try_convert(val); assert!(res.is_err()); let val = ruby.eval::("Float::INFINITY").unwrap(); let res = f64::try_convert(val); assert_eq!(res.unwrap(), f64::INFINITY); let val = ruby.eval::("Float::NAN").unwrap(); let res = f64::try_convert(val); assert!(res.unwrap().is_nan()); } magnus-0.7.1/tests/float_convert_to_value.rs000064400000000000000000000006321046102023000173770ustar 00000000000000#[test] fn it_converts_floats_to_value() { let ruby = unsafe { magnus::embed::init() }; magnus::rb_assert!(ruby, "val == 0.5", val = 0.5); magnus::rb_assert!( ruby, "val == 18446744073709552000.0", val = 18446744073709552000.0 ); magnus::rb_assert!(ruby, "val == Float::INFINITY", val = f64::INFINITY); magnus::rb_assert!(ruby, "val.nan?", val = f64::NAN); } magnus-0.7.1/tests/fmt.rs000064400000000000000000000004211046102023000134160ustar 00000000000000use magnus::Value; #[test] fn it_formats_with_to_s() { let ruby = unsafe { magnus::embed::init() }; let val = ruby .eval::(r#"["foo".upcase, "bar".to_sym, 1 + 2]"#) .unwrap(); assert_eq!(r#"["FOO", :bar, 3]"#, format!("{:?}", val)); } magnus-0.7.1/tests/freeze.rs000064400000000000000000000013051046102023000141120ustar 00000000000000use magnus::{prelude::*, rb_assert, Value}; #[test] fn it_can_check_frozen() { let ruby = unsafe { magnus::embed::init() }; assert!(ruby.eval::(r#"42"#).unwrap().is_frozen()); assert!(ruby.eval::(r#":foo"#).unwrap().is_frozen()); assert!(!ruby.eval::(r#"Object.new"#).unwrap().is_frozen()); assert!(!ruby.eval::(r#"[1]"#).unwrap().is_frozen()); assert!(ruby .eval::(r#"Object.new.freeze"#) .unwrap() .is_frozen()); assert!(ruby.eval::(r#"[1].freeze"#).unwrap().is_frozen()); let val = ruby.ary_new(); rb_assert!(ruby, "!val.frozen?", val); val.freeze(); rb_assert!(ruby, "val.frozen?", val); } magnus-0.7.1/tests/hash.rs000064400000000000000000000012271046102023000135600ustar 00000000000000use std::collections::HashMap; #[test] fn it_converts_hash_map() { let ruby = unsafe { magnus::embed::init() }; let map: HashMap = ruby .eval(r#"{"foo" => 1, "bar" => 2, "baz" => 3}"#) .unwrap(); let mut expected = HashMap::new(); expected.insert("foo".to_owned(), 1); expected.insert("bar".to_owned(), 2); expected.insert("baz".to_owned(), 3); assert_eq!(expected, map); let mut map = HashMap::new(); map.insert("test", (0, 0.5)); map.insert("example", (1, 3.75)); magnus::rb_assert!( ruby, r#"map == {"test" => [0, 0.5], "example" => [1, 3.75]}"#, map ); } magnus-0.7.1/tests/integer_convert_to_value.rs000064400000000000000000000005101046102023000177220ustar 00000000000000#[test] fn it_converts_integers_to_value() { let ruby = unsafe { magnus::embed::init() }; magnus::rb_assert!(ruby, "val == 0", val = 0u8); magnus::rb_assert!(ruby, "val == -1", val = -1i64); magnus::rb_assert!( ruby, "val == 9223372036854775807", val = 9223372036854775807i64 ); } magnus-0.7.1/tests/integer_traits.rs000064400000000000000000000152741046102023000156670ustar 00000000000000use std::os::raw::c_long; use magnus::{eval, Error, Integer, Ruby}; const RUBY_FIXNUM_MAX: u64 = (c_long::MAX / 2) as u64; const RUBY_FIXNUM_MIN: i64 = (c_long::MIN / 2) as i64; #[test] fn test_all() { magnus::Ruby::init(|ruby| { test_add(ruby)?; test_sub(ruby)?; test_mul(ruby)?; test_div(ruby)?; test_ord(ruby)?; Ok(()) }) .unwrap(); } fn test_add(ruby: &Ruby) -> Result<(), Error> { assert_eq!( ruby.integer_from_i64(1) + ruby.integer_from_i64(2), ruby.integer_from_i64(3) ); assert_eq!( ruby.integer_from_i64(-1) + ruby.integer_from_i64(-2), ruby.integer_from_i64(-3) ); assert_eq!( ruby.integer_from_i64(1) + ruby.integer_from_i64(-2), ruby.integer_from_i64(-1) ); assert_eq!( ruby.integer_from_i64(-1) + ruby.integer_from_i64(2), ruby.integer_from_i64(1) ); assert_eq!( ruby.integer_from_u64(RUBY_FIXNUM_MAX) + ruby.integer_from_i64(1), ruby.integer_from_u64(RUBY_FIXNUM_MAX + 1) ); assert_eq!( ruby.integer_from_i64(RUBY_FIXNUM_MIN) + ruby.integer_from_i64(-1), ruby.integer_from_i64(RUBY_FIXNUM_MIN - 1) ); let a: Integer = ruby.eval("2**1000")?; let b: Integer = ruby.eval("2**1000")?; let a_b: Integer = ruby.eval("2**1000 + 2**1000")?; assert_eq!(a + b, a_b); let a: Integer = ruby.eval("2**1000")?; let b = ruby.integer_from_i64(1); let a_b: Integer = ruby.eval("2**1000 + 1")?; assert_eq!(a + b, a_b); let a = ruby.integer_from_i64(1); let b: Integer = ruby.eval("2**1000")?; let a_b: Integer = ruby.eval("2**1000 + 1")?; assert_eq!(a + b, a_b); let mut a = ruby.integer_from_i64(1); a += ruby.integer_from_i64(1); assert_eq!(a, ruby.integer_from_i64(2)); Ok(()) } fn test_sub(ruby: &Ruby) -> Result<(), Error> { assert_eq!( ruby.integer_from_i64(1) - ruby.integer_from_i64(2), ruby.integer_from_i64(-1) ); assert_eq!( ruby.integer_from_i64(-1) - ruby.integer_from_i64(-2), ruby.integer_from_i64(1) ); assert_eq!( ruby.integer_from_i64(1) - ruby.integer_from_i64(-2), ruby.integer_from_i64(3) ); assert_eq!( ruby.integer_from_i64(-1) - ruby.integer_from_i64(2), ruby.integer_from_i64(-3) ); assert_eq!( ruby.integer_from_u64(RUBY_FIXNUM_MAX) - ruby.integer_from_i64(-1), ruby.integer_from_u64(RUBY_FIXNUM_MAX + 1) ); assert_eq!( ruby.integer_from_i64(RUBY_FIXNUM_MIN) - ruby.integer_from_i64(1), ruby.integer_from_i64(RUBY_FIXNUM_MIN - 1) ); let a: Integer = ruby.eval("2**1000")?; let b: Integer = ruby.eval("2**999")?; let a_b: Integer = ruby.eval("2**1000 - 2**999")?; assert_eq!(a - b, a_b); let a: Integer = ruby.eval("2**1000")?; let b = ruby.integer_from_i64(1); let a_b: Integer = ruby.eval("2**1000 - 1")?; assert_eq!(a - b, a_b); let a = ruby.integer_from_i64(1); let b: Integer = ruby.eval("2**1000")?; let a_b: Integer = ruby.eval("1 - 2**1000")?; assert_eq!(a - b, a_b); let mut a = ruby.integer_from_i64(1); a -= ruby.integer_from_i64(1); assert_eq!(a, ruby.integer_from_i64(0)); Ok(()) } fn test_mul(ruby: &Ruby) -> Result<(), Error> { assert_eq!( ruby.integer_from_i64(1) * ruby.integer_from_i64(2), ruby.integer_from_i64(2) ); assert_eq!( ruby.integer_from_i64(-1) * ruby.integer_from_i64(-2), ruby.integer_from_i64(2) ); assert_eq!( ruby.integer_from_i64(1) * ruby.integer_from_i64(-2), ruby.integer_from_i64(-2) ); assert_eq!( ruby.integer_from_i64(-1) * ruby.integer_from_i64(2), ruby.integer_from_i64(-2) ); assert_eq!( ruby.integer_from_u64(RUBY_FIXNUM_MAX) * ruby.integer_from_i64(4), Integer::from_value(eval(&format!("{} * 4", RUBY_FIXNUM_MAX)).unwrap()).unwrap() ); assert_eq!( ruby.integer_from_i64(RUBY_FIXNUM_MIN) * ruby.integer_from_i64(4), Integer::from_value(eval(&format!("{} * 4", RUBY_FIXNUM_MIN)).unwrap()).unwrap() ); let a: Integer = ruby.eval("2**1000")?; let b: Integer = ruby.eval("2**999")?; let a_b: Integer = ruby.eval("2**1000 * 2**999")?; assert_eq!(a * b, a_b); let a: Integer = ruby.eval("2**1000")?; let b = ruby.integer_from_i64(2); let a_b: Integer = ruby.eval("2**1000 * 2")?; assert_eq!(a * b, a_b); let a = ruby.integer_from_i64(2); let b: Integer = ruby.eval("2**1000")?; let a_b: Integer = ruby.eval("2 * 2**1000")?; assert_eq!(a * b, a_b); let mut a = ruby.integer_from_i64(1); a *= ruby.integer_from_i64(2); assert_eq!(a, ruby.integer_from_i64(2)); Ok(()) } fn test_div(ruby: &Ruby) -> Result<(), Error> { assert_eq!( ruby.integer_from_i64(1) / ruby.integer_from_i64(2), ruby.integer_from_i64(0) ); assert_eq!( ruby.integer_from_i64(-4) / ruby.integer_from_i64(2), ruby.integer_from_i64(-2) ); assert_eq!( ruby.integer_from_i64(7) / ruby.integer_from_i64(2), ruby.integer_from_i64(3) ); let a: Integer = ruby.eval("2**1000")?; let b: Integer = ruby.eval("2**999")?; let a_b: Integer = ruby.eval("2**1000 / 2**999")?; assert_eq!(a / b, a_b); let a: Integer = ruby.eval("2**1000")?; let b = ruby.integer_from_i64(2); let a_b: Integer = ruby.eval("2**1000 / 2")?; assert_eq!(a / b, a_b); let a = ruby.integer_from_i64(2); let b: Integer = ruby.eval("2**1000")?; let a_b: Integer = ruby.eval("2 / 2**1000")?; assert_eq!(a / b, a_b); let mut a = ruby.integer_from_i64(4); a /= ruby.integer_from_i64(2); assert_eq!(a, ruby.integer_from_i64(2)); Ok(()) } fn test_ord(ruby: &Ruby) -> Result<(), Error> { assert!(ruby.integer_from_i64(1) < ruby.integer_from_i64(2)); assert!(ruby.integer_from_i64(2) > ruby.integer_from_i64(1)); assert!(ruby.integer_from_i64(1) <= ruby.integer_from_i64(2)); assert!(ruby.integer_from_i64(2) >= ruby.integer_from_i64(1)); assert!(ruby.integer_from_i64(1) <= ruby.integer_from_i64(1)); assert!(ruby.integer_from_i64(1) == ruby.integer_from_i64(1)); assert!(ruby.integer_from_i64(1) >= ruby.integer_from_i64(1)); let a: Integer = ruby.eval("2**1000")?; let b: Integer = ruby.eval("2**999")?; let c: Integer = ruby.eval("2**1000")?; assert!(a > b); assert!(a >= b); assert!(b < a); assert!(b <= a); assert!(a == c); assert!(a >= c); assert!(a <= c); assert!(a > ruby.integer_from_i64(1)); assert!(a >= ruby.integer_from_i64(1)); assert!(ruby.integer_from_i64(1) < a); assert!(ruby.integer_from_i64(1) <= a); Ok(()) } magnus-0.7.1/tests/ivar.rs000064400000000000000000000007551046102023000136030ustar 00000000000000use magnus::{eval, prelude::*, rb_assert, RObject, Value}; #[test] fn it_modifies_ivars() { let ruby = unsafe { magnus::embed::init() }; let val: RObject = eval!(ruby, "$val = Object.new").unwrap(); val.ivar_set("@test", 42).unwrap(); rb_assert!(ruby, "val.instance_variable_get(:@test) == 42", val); let _: Value = eval!(ruby, r#"val.instance_variable_set(:@example, "test")"#, val).unwrap(); assert_eq!("test", val.ivar_get::<_, String>("@example").unwrap()) } magnus-0.7.1/tests/lazy_id.rs000064400000000000000000000003131046102023000142630ustar 00000000000000use magnus::{rb_assert, value::LazyId}; static FOO: LazyId = LazyId::new("foo"); #[test] fn it_works() { let _ruby = unsafe { magnus::embed::init() }; rb_assert!("foo == :foo", foo = *FOO); } magnus-0.7.1/tests/make_proc.rs000064400000000000000000000006421046102023000145750ustar 00000000000000use magnus::{function, rb_assert, Ruby}; #[test] fn it_makes_a_proc() { let ruby = unsafe { magnus::embed::init() }; ruby.define_global_function("make_proc", function!(Ruby::block_proc, 0)); rb_assert!(ruby, "Proc === make_proc { 1 + 1 }"); rb_assert!(ruby, "(make_proc { 1 + 1 }).call == 2"); rb_assert!( ruby, "begin; make_proc; rescue => e; end; ArgumentError === e" ); } magnus-0.7.1/tests/proc_new.rs000064400000000000000000000011031046102023000144420ustar 00000000000000use magnus::{block::Proc, eval, value::Opaque, Ruby, Value}; fn make_proc(ruby: &Ruby) -> Proc { let x = String::from("foo"); let y = Opaque::from(ruby.str_new("bar")); ruby.proc_from_fn(move |_ruby, _args, _block| Ok((x.clone(), y))) } #[test] fn proc_from_closure_can_be_called_later() { let ruby = unsafe { magnus::embed::init() }; let proc = make_proc(&ruby); eval::(r#"1024.times.map {|i| "test#{i}"}"#).unwrap(); ruby.gc_start(); let res: bool = eval!(ruby, r#"proc.call == ["foo", "bar"]"#, proc).unwrap(); assert!(res); } magnus-0.7.1/tests/range.rs000064400000000000000000000007311046102023000137300ustar 00000000000000#[test] fn it_converts_ranges() { let ruby = unsafe { magnus::embed::init() }; magnus::rb_assert!(ruby, "range == (2...7)", range = 2..7); magnus::rb_assert!(ruby, "range == (2..7)", range = 2..=7); magnus::rb_assert!(ruby, "range == (2..)", range = 2..); magnus::rb_assert!(ruby, "range == (...7)", range = ..7); magnus::rb_assert!(ruby, "range == (..7)", range = ..=7); magnus::rb_assert!(ruby, "range == Range.new(nil, nil)", range = ..); } magnus-0.7.1/tests/return_custom_error.rs000064400000000000000000000012361046102023000167570ustar 00000000000000use magnus::{error::IntoError, function, rb_assert, Error, Ruby}; struct CustomError(&'static str); impl IntoError for CustomError { fn into_error(self, ruby: &Ruby) -> Error { Error::new( ruby.exception_runtime_error(), format!("Custom error: {}", self.0), ) } } fn example() -> Result<(), CustomError> { Err(CustomError("test")) } #[test] fn it_can_bind_function_returning_custom_error() { let ruby = unsafe { magnus::embed::init() }; ruby.define_global_function("example", function!(example, 0)); rb_assert!( ruby, r#"(example rescue $!).message == "Custom error: test""# ); } magnus-0.7.1/tests/return_iter.rs000064400000000000000000000020401046102023000151710ustar 00000000000000use magnus::{block::Yield, eval, method, prelude::*, rb_assert, Ruby, Value}; fn count_to_3(ruby: &Ruby, rb_self: Value) -> Yield> { if ruby.block_given() { Yield::Iter((1..=3).into_iter()) } else { Yield::Enumerator(rb_self.enumeratorize("count_to_3", ())) } } #[test] fn it_converts_iterator_to_yields() { let ruby = unsafe { magnus::embed::init() }; ruby.define_global_function("count_to_3", method!(count_to_3, 0)); let a = ruby.ary_new(); let _: Value = eval!( ruby, " count_to_3 do |i| a << i end ", a ) .unwrap(); rb_assert!(ruby, "a == [1,2,3]", a); let enumerator: Value = eval!( ruby, " def raises yield false rescue StopIteration true end count_to_3 " ) .unwrap(); rb_assert!(ruby, "enumerator.next == 1 && enumerator.next == 2 && enumerator.next == 3 && raises { enumerator.next }", enumerator); } magnus-0.7.1/tests/scan_args.rs000064400000000000000000000030171046102023000145740ustar 00000000000000use magnus::{ block::Proc, error::Error, method, scan_args::{get_kwargs, scan_args}, value::Value, RArray, RHash, Ruby, Symbol, }; fn example(ruby: &Ruby, _rb_self: Value, args: &[Value]) -> Result { let args = scan_args(args)?; let (a,): (String,) = args.required; let (b,): (Option,) = args.optional; let splat: RArray = args.splat; let (c,): (Symbol,) = args.trailing; let kw = get_kwargs::<_, (usize,), (Option, Option, Option), RHash>( args.keywords, &["d"], &["e", "f", "g"], )?; let (d,) = kw.required; let (e, f, g) = kw.optional; let kw_splat = kw.splat; let _: Option = args.block; let res = ruby.ary_new_capa(7); res.push(a)?; res.push(b)?; res.push(splat)?; res.push(c)?; res.push(d)?; if let Some(e) = e { res.push(e)?; } res.push(f)?; if let Some(g) = g { res.push(g)?; } res.push(kw_splat)?; Ok(res) } #[test] fn it_scans_args() { let ruby = unsafe { magnus::embed::init() }; ruby.define_global_function("example", method!(example, -1)); let res = ruby.eval::(r#" example("a", "b", "splat1", "splat2", :c, d: 1, f: 2, h: 3) == ["a", "b", ["splat1", "splat2"], :c, 1, 2, {h: 3}] "#).unwrap(); assert!(res); let res = ruby .eval::( r#" example("a", :c, d: 1) == ["a", nil, [], :c, 1, nil, {}] "#, ) .unwrap(); assert!(res); } magnus-0.7.1/tests/str.rs000064400000000000000000000003561046102023000134470ustar 00000000000000use magnus::RString; #[test] fn it_converts_to_ref_str() { let ruby = unsafe { magnus::embed::init() }; unsafe { let s: RString = ruby.eval("'hello'").unwrap(); assert_eq!("hello", s.as_str().unwrap()); } } magnus-0.7.1/tests/string.rs000064400000000000000000000004641046102023000141450ustar 00000000000000use magnus::{prelude::*, Value}; #[test] fn it_converts_to_utf8_string() { let ruby = unsafe { magnus::embed::init() }; let val: Value = ruby .eval(r#""caf\xE9".force_encoding("ISO-8859-1")"#) .unwrap(); let s = String::try_convert(val).unwrap(); assert_eq!("café", s); } magnus-0.7.1/tests/struct.rs000064400000000000000000000015271046102023000141640ustar 00000000000000use magnus::{prelude::*, r_struct::RStruct, rb_assert}; #[test] fn it_defines_a_struct() { let ruby = unsafe { magnus::embed::init() }; let struct_class = ruby.define_struct(Some("Foo"), ("bar", "baz")).unwrap(); rb_assert!(ruby, r#"val.name == "Struct::Foo""#, val = struct_class); rb_assert!(ruby, "val.members == [:bar, :baz]", val = struct_class); let obj = struct_class.new_instance((1, 2)).unwrap(); rb_assert!(ruby, "val.bar == 1", val = obj); rb_assert!(ruby, "val.baz == 2", val = obj); rb_assert!( ruby, r#"val.name == nil"#, val = ruby.define_struct(None, ("foo",)).unwrap() ); let obj = RStruct::from_value(obj).unwrap(); assert_eq!(1, obj.get(0).unwrap()); assert_eq!(2, obj.get(1).unwrap()); assert_eq!(&["bar", "baz"], obj.members().unwrap().as_slice()) } magnus-0.7.1/tests/symbol.rs000064400000000000000000000021241046102023000141370ustar 00000000000000use magnus::{rb_assert, StaticSymbol, Symbol, Value}; #[test] fn it_makes_a_symbol() { let ruby = unsafe { magnus::embed::init() }; let sym = ruby.to_symbol("foo"); // not static by default assert!(!sym.is_static()); rb_assert!(ruby, "sym == :foo", sym); ruby.eval::(":bar").unwrap(); let sym = ruby.to_symbol("bar"); // static because there's a previous Ruby symbol literal assert!(sym.is_static()); ruby.sym_new("baz"); let sym = ruby.to_symbol("baz"); // static because there's a previous StaticSymbol assert!(sym.is_static()); let sym: Symbol = ruby.sym_new("qux").into(); assert!(sym.is_static()); let sym = ruby.to_symbol("example"); assert!(!sym.is_static()); sym.to_static(); assert!(sym.is_static()); let x = ruby.eval::(r#""xxx".to_sym"#).unwrap(); assert!(!x.is_static()); ruby.eval::(":xxx").unwrap(); let y = ruby.eval::(r#""yyy".to_sym"#).unwrap(); assert!(!y.is_static()); StaticSymbol::from_value(ruby.eval::(":yyy").unwrap()).unwrap(); } magnus-0.7.1/tests/try_convert_array.rs000064400000000000000000000007441046102023000164140ustar 00000000000000use magnus::RArray; #[test] fn it_converts_to_array() { let ruby = unsafe { magnus::embed::init() }; assert!(ruby.eval::("[]").is_ok()); assert!(ruby.eval::("[nil]").is_ok()); assert!(ruby.eval::("[1, 2]").is_ok()); assert!(ruby.eval::(r#"["foo", "bar", "baz"]"#).is_ok()); assert!(ruby.eval::("nil").is_err()); assert!(ruby.eval::("1").is_err()); assert!(ruby.eval::(r#""foo""#).is_err()); } magnus-0.7.1/tests/tuple_to_array.rs000064400000000000000000000002631046102023000156650ustar 00000000000000#[test] fn it_converts_tuple_to_array() { let ruby = unsafe { magnus::embed::init() }; magnus::rb_assert!(ruby, "val == [1, 2.3, nil, [4]]", val = (1, 2.3, (), (4,))); } magnus-0.7.1/tests/typed_data.rs000064400000000000000000000011541046102023000147520ustar 00000000000000use magnus::{embed::init, eval, rb_assert, Ruby, Value}; #[magnus::wrap(class = "Example", free_immediately)] struct Example { value: String, } fn make_rb_example(ruby: &Ruby, value: &str) -> Value { let ex = Example { value: value.to_owned(), }; ruby.into_value(ex) } #[test] fn it_wraps_rust_struct() { let ruby = unsafe { init() }; ruby.define_class("Example", ruby.class_object()).unwrap(); let val = make_rb_example(&ruby, "foo"); rb_assert!(ruby, "val.class == Example", val); let ex: &Example = eval!(ruby, "val", val).unwrap(); assert_eq!("foo", ex.value) } magnus-0.7.1/tests/typed_data_obj.rs000064400000000000000000000034051046102023000156050ustar 00000000000000use magnus::{ embed::init, function, gc, method, prelude::*, typed_data::Obj, value::Opaque, DataTypeFunctions, TypedData, }; #[magnus::wrap(class = "Point", free_immediately)] struct Point { x: isize, y: isize, } impl Point { fn new(x: isize, y: isize) -> Self { Self { x, y } } } #[derive(TypedData)] #[magnus(class = "Line", free_immediately, mark)] struct Line { #[magnus(opaque_attr_reader)] start: Opaque>, #[magnus(opaque_attr_reader)] end: Opaque>, } impl Line { fn new(start: Obj, end: Obj) -> Self { Self { start: start.into(), end: end.into(), } } fn length(&self) -> f64 { let start = self.start(); let end = self.end(); (((end.x - start.x).pow(2) + (end.y - start.y).pow(2)) as f64).sqrt() } } impl DataTypeFunctions for Line { fn mark(&self, marker: &gc::Marker) { marker.mark(self.start); marker.mark(self.end); } } #[test] fn it_can_nest_wrapped_structs() { let ruby = unsafe { init() }; let class = ruby.define_class("Point", ruby.class_object()).unwrap(); class .define_singleton_method("new", function!(Point::new, 2)) .unwrap(); let class = ruby.define_class("Line", ruby.class_object()).unwrap(); class .define_singleton_method("new", function!(Line::new, 2)) .unwrap(); class .define_method("length", method!(Line::length, 0)) .unwrap(); let result: f64 = ruby .eval( r#" start = Point.new(0, 0) finish = Point.new(10, 10) line = Line.new(start, finish) line.length "#, ) .unwrap(); assert!(result - 14.14213 < 0.00001); } magnus-0.7.1/tests/typed_data_subclass.rs000064400000000000000000000030621046102023000166510ustar 00000000000000use magnus::{embed::init, method, prelude::*, rb_assert}; #[magnus::wrap(class = "Pleasantry")] enum Pleasantry { #[magnus(class = "Greeting")] Greeting(String), #[magnus(class = "Farewell")] Farewell(String), } impl Pleasantry { fn to_s(&self) -> String { match self { Self::Greeting(subject) => format!("Hello, {}!", subject), Self::Farewell(subject) => format!("Goodbye, {}!", subject), } } } #[test] fn it_wraps_rust_struct() { let ruby = unsafe { init() }; let class = ruby .define_class("Pleasantry", ruby.class_object()) .unwrap(); ruby.define_class("Farewell", class).unwrap(); ruby.define_class("Greeting", class).unwrap(); class .define_method("to_s", method!(Pleasantry::to_s, 0)) .unwrap(); let greeting = Pleasantry::Greeting("World".to_owned()); rb_assert!(ruby, "greeting.is_a?(Greeting)", greeting); let greeting = Pleasantry::Greeting("World".to_owned()); rb_assert!(ruby, "greeting.is_a?(Pleasantry)", greeting); let greeting = Pleasantry::Greeting("World".to_owned()); rb_assert!(ruby, r#"greeting.to_s == "Hello, World!""#, greeting); let farewell = Pleasantry::Farewell("World".to_owned()); rb_assert!(ruby, "farewell.is_a?(Farewell)", farewell); let farewell = Pleasantry::Farewell("World".to_owned()); rb_assert!(ruby, "farewell.is_a?(Pleasantry)", farewell); let farewell = Pleasantry::Farewell("World".to_owned()); rb_assert!(ruby, r#"farewell.to_s == "Goodbye, World!""#, farewell); } magnus-0.7.1/tests/typed_data_subclass_from_ruby.rs000064400000000000000000000041711046102023000207370ustar 00000000000000use magnus::{ embed::init, eval, function, method, prelude::*, rb_assert, typed_data::Obj, Error, RClass, Ruby, Value, }; const FACTOR: f64 = 1000000.0; #[magnus::wrap(class = "Temperature")] struct Temperature { microkelvin: u64, } impl Temperature { fn new(class: RClass, k: f64) -> Result, Error> { let ruby = Ruby::get_with(class); if k < 0.0 { return Err(Error::new( ruby.exception_arg_error(), "temperature must be above absolute zero", )); } let value = Self { microkelvin: (k * FACTOR) as u64, }; Ok(ruby.obj_wrap_as(value, class)) } fn to_kelvin(&self) -> f64 { self.microkelvin as f64 / FACTOR } fn to_s(&self) -> String { format!("{} K", self.to_kelvin()) } } #[test] fn it_wraps_rust_struct() { let ruby = unsafe { init() }; let class = ruby .define_class("Temperature", ruby.class_object()) .unwrap(); class .define_singleton_method("new", method!(Temperature::new, 1)) .unwrap(); // define the `inherited` callback method so that when Temperature is // inherited from we undef the `alloc` method for the subclass. This // a) prevents uninitialised instances of the subclass being created // b) silences a warning on Ruby 3.2 class .define_singleton_method("inherited", function!(RClass::undef_default_alloc_func, 1)) .unwrap(); class .define_method("to_kelvin", method!(Temperature::to_kelvin, 0)) .unwrap(); class .define_method("to_s", method!(Temperature::to_s, 0)) .unwrap(); let _: Value = eval!( ruby, r##" class Celsius < Temperature def self.new(c) super(c + 273.15) end def to_s "#{to_kelvin - 273.15} °C" end end "##, ) .unwrap(); rb_assert!(ruby, r#"Celsius.new(19.5).to_s == "19.5 °C""#); rb_assert!(ruby, r"Celsius.new(19.5).class == Celsius"); rb_assert!(ruby, r"Celsius.new(19.5).to_kelvin == 292.65"); } magnus-0.7.1/tests/yield.rs000064400000000000000000000014301046102023000137370ustar 00000000000000use magnus::{eval, method, rb_assert, Error, Ruby, Value}; fn flipflop(ruby: &Ruby, _rb_self: Value, mut val: bool) -> Result<(), Error> { val = ruby.yield_value(val)?; loop { val = ruby.yield_value(!val)?; } } #[test] fn it_yields() { let ruby = unsafe { magnus::embed::init() }; ruby.define_global_function("flipflop", method!(flipflop, 1)); let values = ruby.ary_new(); let i: Value = eval!( ruby, " i = 0 flipflop(true) do |val| values << val i += 1 if val break if i > 5 val end i ", values ) .unwrap(); rb_assert!(ruby, "i == 6 && values == [true, false, true, false, true, false, true, false, true, false, true]", i, values); }