diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7428eb66..44af1cd8 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,69 +1,87 @@ name: Test on: - - push - - pull_request + - push + - pull_request jobs: - test: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: - - ubuntu-latest - - macos-latest - - windows-latest - ruby-version: - - "2.7" - - "3.0" - - "3.1" - - "3.2" - - head - rustup-toolchain: - - "1.61" - - stable - include: - - os: windows-latest - ruby-version: mswin - rustup-toolchain: stable - exclude: - - os: windows-latest - ruby-version: head + test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + # intel + - macos-12 + # arm + - macos-14 + - windows-latest + ruby-version: + - "2.7" + - "3.0" + - "3.1" + - "3.2" + - "3.3" + - head + rustup-toolchain: + - "1.61" + - stable + exclude: + - os: windows-latest + ruby-version: "3.3" + - os: windows-latest + ruby-version: head + # The setup-ruby pre-built Ruby 3.1 is built on macos-11 + # and something doesn't quite add up when running it on + # macos-14 just for our tests and everything fails with + # dyld: missing symbol called + # unfortunately macos doesn't print what symbol is missing + # so it's hard to debug further + # everything works fine when Ruby 3.1 is built on the same + # version of macos it's running on + - os: macos-14 + ruby-version: "3.1" + rustup-toolchain: stable - steps: - - uses: actions/checkout@v3 + steps: + - uses: actions/checkout@v4 - - uses: oxidize-rb/actions/setup-ruby-and-rust@v1 - with: - rustup-toolchain: ${{ matrix.rustup-toolchain }} - ruby-version: ${{ matrix.ruby-version }} - cache-version: v2 - bundler-cache: true - cargo-cache: true - cargo-cache-extra-path: | - examples/rust_blank/tmp/ - examples/complete_object/tmp/ - examples/custom_exception_ruby/tmp/ - examples/custom_exception_rust/tmp/ + - uses: oxidize-rb/actions/setup-ruby-and-rust@v1 + with: + rustup-toolchain: ${{ matrix.rustup-toolchain }} + ruby-version: ${{ matrix.ruby-version }} + cache-version: v2 + bundler-cache: true + cargo-cache: true + cargo-cache-extra-path: | + examples/rust_blank/tmp/ + examples/complete_object/tmp/ + examples/custom_exception_ruby/tmp/ + examples/custom_exception_rust/tmp/ - - name: Example gem tests (blank?) - working-directory: examples/rust_blank - run: bundle exec rake test + # Ruby head is currently missing the power_assert default gem + - name: Ruby head bug workaround + if: matrix.ruby-version == 'head' + run: gem install power_assert - - name: Example gem tests (complete object) - working-directory: examples/complete_object - run: bundle exec rake test + - name: Example gem tests (blank?) + working-directory: examples/rust_blank + run: bundle exec rake test - - name: Example gem tests (custom exception defined in Ruby) - if: matrix.ruby-version != 'head' - working-directory: examples/custom_exception_ruby - run: bundle exec rake test + - name: Example gem tests (complete object) + working-directory: examples/complete_object + run: bundle exec rake test - - name: Example gem tests (custom exception defined in Rust) - if: matrix.ruby-version != 'head' - working-directory: examples/custom_exception_rust - run: bundle exec rake test + - name: Example gem tests (custom exception defined in Ruby) + if: matrix.ruby-version != 'head' + working-directory: examples/custom_exception_ruby + run: bundle exec rake test - - name: Run tests - run: cargo test --workspace + - name: Example gem tests (custom exception defined in Rust) + if: matrix.ruby-version != 'head' + working-directory: examples/custom_exception_rust + run: bundle exec rake test + + - name: Run tests + run: cargo test --workspace diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a6c2e87..1c3a9a59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,87 @@ ### 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`. @@ -76,7 +157,7 @@ ### 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()`, +- 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`. @@ -398,7 +479,14 @@ - 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.6.0...HEAD +[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 diff --git a/Cargo.lock b/Cargo.lock index 765696e3..fae067be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 3 [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" dependencies = [ "memchr", ] [[package]] name = "bindgen" -version = "0.62.0" +version = "0.69.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6720a8b7b2d39dd533285ed438d458f65b31b5c257e6ac7bb3d7e82844dd722" +checksum = "9ffcebc3849946a7170a05992aac39da343a90676ab392c51a4280981d6379c2" dependencies = [ "bitflags", "cexpr", @@ -28,14 +28,14 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 1.0.109", + "syn", ] [[package]] name = "bitflags" -version = "1.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "bytes" @@ -105,7 +105,7 @@ dependencies = [ [[package]] name = "magnus" -version = "0.6.0" +version = "0.7.1" dependencies = [ "bytes", "magnus", @@ -122,7 +122,7 @@ dependencies = [ "magnus", "proc-macro2", "quote", - "syn 2.0.27", + "syn", ] [[package]] @@ -173,18 +173,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.79" +version = "0.9.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "939fb78db3e4f26665c1d4c7b91ca66d3578335a19aba552d4a6445811d07072" +checksum = "05b780e6858b0b0eced1d55d0f097c024b77a37b41f83bd35341130f78e37c51" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.79" +version = "0.9.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335a95eb0420d52fa94ef12019df3c2c250c6b19cbb3c60bd05cb7e9c362072c" +checksum = "44957a3bc513dad1b0f20bdd0ee3b82e729a59da44086a6b40d8bc71958a6db8" dependencies = [ "bindgen", "lazy_static", @@ -192,7 +192,7 @@ dependencies = [ "quote", "regex", "shell-words", - "syn 1.0.109", + "syn", ] [[package]] @@ -203,9 +203,9 @@ checksum = "a35802679f07360454b418a5d1735c89716bde01d35b1560fc953c1415a0b3bb" [[package]] name = "regex" -version = "1.9.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ "aho-corasick", "memchr", @@ -215,9 +215,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.3" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" dependencies = [ "aho-corasick", "memchr", @@ -256,20 +256,9 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.27" +version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 2b453663..8b13691f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "magnus" -version = "0.6.0" +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"] @@ -13,28 +13,42 @@ 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"] +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 = ["friendly-api"] +default = ["old-api"] bytes = ["dep:bytes"] embed = ["rb-sys/link-ruby"] -friendly-api = [] +old-api = [] rb-sys = [] -ruby-static = ["rb-sys/ruby-static"] -deprecated-send-sync-value = [] [dependencies] bytes = { version = "1", optional = true } magnus-macros = { version = "0.6.0", path = "magnus-macros" } -rb-sys = { version = "0.9.77", default-features = false, features = ["bindgen-rbimpls", "bindgen-deprecated-types"] } +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 = ".", features = ["embed", "rb-sys"] } +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.1" +rb-sys-env = "0.1.2" [lib] doc-scrape-examples = false diff --git a/README.md b/README.md index f809e40e..202287c1 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,11 @@ fn fib(n: usize) -> usize { } } -magnus::define_global_function("fib", magnus::function!(fib, 1)); +#[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): @@ -46,9 +50,14 @@ fn is_blank(rb_self: String) -> bool { !rb_self.contains(|c: char| !c.is_whitespace()) } -let class = magnus::define_class("String", magnus::class::object())?; -// 0 as self doesn't count against the number of arguments -class.define_method("blank?", magnus::method!(is_blank, 0))?; +#[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 @@ -82,7 +91,7 @@ wrapped in the specified class, and whenever it is passed back to Rust it will be unwrapped to a reference. ```rust -use magnus::{class, define_class, function, method, prelude::*, Error}; +use magnus::{function, method, prelude::*, Error, Ruby}; #[magnus::wrap(class = "Point")] struct Point { @@ -109,8 +118,8 @@ impl Point { } #[magnus::init] -fn init() -> Result<(), Error> { - let class = define_class("Point", class::object())?; +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))?; @@ -137,6 +146,37 @@ impl MutPoint { } ``` +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) @@ -151,7 +191,7 @@ setting the `crate-type` attribute in your `Cargo.toml`. crate-type = ["cdylib"] [dependencies] -magnus = "0.6" +magnus = "0.7" ``` When Ruby loads your extension it calls an 'init' function defined in your @@ -162,15 +202,15 @@ your init function so it can be correctly exposed to Ruby. **`src/lib.rs`** ```rust -use magnus::{define_global_function, function}; +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() { - define_global_function("distance", function!(distance, 2)); +fn init(ruby: &Ruby) -> Result<(), Error> { + ruby.define_global_function("distance", function!(distance, 2)); } ``` @@ -234,7 +274,7 @@ To call Ruby from a Rust program, enable the `embed` feature: ```toml [dependencies] -magnus = { version = "0.6", features = ["embed"] } +magnus = { version = "0.7", features = ["embed"] } ``` This enables linking to Ruby and gives access to the `embed` module. @@ -245,14 +285,16 @@ called more than once. **`src/main.rs`** ```rust -use magnus::{embed, eval}; +use magnus::eval; fn main() { - let _cleanup = unsafe { embed::init() }; + magnus::Ruby::init(|ruby| { + let val: f64 = eval!(ruby, "a + rand", a = 1)?; - let val: f64 = eval!("a + rand", a = 1).unwrap(); + println!("{}", val); - println!("{}", val); + Ok(()) + }).unwrap(); } ``` @@ -287,6 +329,7 @@ See `magnus::TryConvert` for more details. | `[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` | @@ -316,10 +359,19 @@ and `magnus::ArgList` for some additional details. | `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 @@ -331,14 +383,14 @@ encoded String so you can take a reference without allocating you could do the following: ```rust -fn example(val: magnus::Value) -> Result<(), magnus::Error> { +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(magnus::exception::type_error(), "expected string"))?; + .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( - magnus::exception::encoding_error(), + ruby.exception_encoding_error(), "string must be utf-8", )); } @@ -380,22 +432,24 @@ use `unsafe`. ## Compatibility -Ruby versions 3.0, 3.1, and 3.2 are fully supported. +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. -Magnus is no longer tested against Ruby 2.6. Code supporting 2.6 has not been -removed, but there is no guarantee it will continue to work. Minor patches for -Ruby 2.6 compatibility will be accepted. - 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. +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]. @@ -418,7 +472,7 @@ which Magnus does not expose. ### `serde_magnus` -`serde_magnus` integrates [Serde] and Magnus for seamless serialisation and +[`serde_magnus`] integrates [Serde] and Magnus for seamless serialisation and deserialisation of Rust to Ruby data structures and vice versa. ## Users @@ -453,7 +507,7 @@ rustflags = ["-C", "link-dead-code=on"] 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 lets hope using this +Ultimately, his hubris lead to his fall to Chaos, but let's hope using this library turns out better for you. ## License diff --git a/examples/complete_object/ext/temperature/Cargo.lock b/examples/complete_object/ext/temperature/Cargo.lock index e2013eee..3bdfd2af 100644 --- a/examples/complete_object/ext/temperature/Cargo.lock +++ b/examples/complete_object/ext/temperature/Cargo.lock @@ -13,29 +13,29 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.62.0" +version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6720a8b7b2d39dd533285ed438d458f65b31b5c257e6ac7bb3d7e82844dd722" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ "bitflags", "cexpr", "clang-sys", + "itertools", "lazy_static", "lazycell", - "peeking_take_while", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 1.0.109", + "syn", ] [[package]] name = "bitflags" -version = "1.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "cexpr" @@ -63,12 +63,27 @@ dependencies = [ "libloading", ] +[[package]] +name = "either" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" + [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -99,7 +114,7 @@ dependencies = [ [[package]] name = "magnus" -version = "0.6.0" +version = "0.7.0" dependencies = [ "magnus-macros", "rb-sys", @@ -113,7 +128,7 @@ version = "0.6.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn", ] [[package]] @@ -138,12 +153,6 @@ dependencies = [ "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" @@ -164,18 +173,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.79" +version = "0.9.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "939fb78db3e4f26665c1d4c7b91ca66d3578335a19aba552d4a6445811d07072" +checksum = "05b780e6858b0b0eced1d55d0f097c024b77a37b41f83bd35341130f78e37c51" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.79" +version = "0.9.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335a95eb0420d52fa94ef12019df3c2c250c6b19cbb3c60bd05cb7e9c362072c" +checksum = "44957a3bc513dad1b0f20bdd0ee3b82e729a59da44086a6b40d8bc71958a6db8" dependencies = [ "bindgen", "lazy_static", @@ -183,7 +192,7 @@ dependencies = [ "quote", "regex", "shell-words", - "syn 1.0.109", + "syn", ] [[package]] @@ -245,17 +254,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.27" @@ -272,6 +270,7 @@ name = "temperature" version = "0.1.0" dependencies = [ "magnus", + "rb-sys", ] [[package]] diff --git a/examples/complete_object/ext/temperature/Cargo.toml b/examples/complete_object/ext/temperature/Cargo.toml index 6b54a6ca..8ff51bdb 100644 --- a/examples/complete_object/ext/temperature/Cargo.toml +++ b/examples/complete_object/ext/temperature/Cargo.toml @@ -8,3 +8,6 @@ crate-type = ["cdylib"] [dependencies] magnus = { path = "../../../.." } +# enable rb-sys feature to test against Ruby head. This is only needed if you +# want to work with the unreleased, in-development, next version of Ruby +rb-sys = { version = "*", default-features = false, features = ["stable-api-compiled-fallback"] } diff --git a/examples/custom_exception_ruby/ext/ahriman/Cargo.lock b/examples/custom_exception_ruby/ext/ahriman/Cargo.lock index af0c6a6d..e5a2a1b1 100644 --- a/examples/custom_exception_ruby/ext/ahriman/Cargo.lock +++ b/examples/custom_exception_ruby/ext/ahriman/Cargo.lock @@ -16,33 +16,34 @@ name = "ahriman" version = "0.1.0" dependencies = [ "magnus", + "rb-sys", ] [[package]] name = "bindgen" -version = "0.62.0" +version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6720a8b7b2d39dd533285ed438d458f65b31b5c257e6ac7bb3d7e82844dd722" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ "bitflags", "cexpr", "clang-sys", + "itertools", "lazy_static", "lazycell", - "peeking_take_while", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 1.0.109", + "syn", ] [[package]] name = "bitflags" -version = "1.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "cexpr" @@ -70,12 +71,27 @@ dependencies = [ "libloading", ] +[[package]] +name = "either" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" + [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -106,7 +122,7 @@ dependencies = [ [[package]] name = "magnus" -version = "0.6.0" +version = "0.7.0" dependencies = [ "magnus-macros", "rb-sys", @@ -120,7 +136,7 @@ version = "0.6.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn", ] [[package]] @@ -145,12 +161,6 @@ dependencies = [ "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" @@ -171,18 +181,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.79" +version = "0.9.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "939fb78db3e4f26665c1d4c7b91ca66d3578335a19aba552d4a6445811d07072" +checksum = "05b780e6858b0b0eced1d55d0f097c024b77a37b41f83bd35341130f78e37c51" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.79" +version = "0.9.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335a95eb0420d52fa94ef12019df3c2c250c6b19cbb3c60bd05cb7e9c362072c" +checksum = "44957a3bc513dad1b0f20bdd0ee3b82e729a59da44086a6b40d8bc71958a6db8" dependencies = [ "bindgen", "lazy_static", @@ -190,7 +200,7 @@ dependencies = [ "quote", "regex", "shell-words", - "syn 1.0.109", + "syn", ] [[package]] @@ -252,17 +262,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.27" diff --git a/examples/custom_exception_ruby/ext/ahriman/Cargo.toml b/examples/custom_exception_ruby/ext/ahriman/Cargo.toml index 9b5b48f3..99a6eaf6 100644 --- a/examples/custom_exception_ruby/ext/ahriman/Cargo.toml +++ b/examples/custom_exception_ruby/ext/ahriman/Cargo.toml @@ -8,3 +8,6 @@ crate-type = ["cdylib"] [dependencies] magnus = { path = "../../../.." } +# enable rb-sys feature to test against Ruby head. This is only needed if you +# want to work with the unreleased, in-development, next version of Ruby +rb-sys = { version = "*", default-features = false, features = ["stable-api-compiled-fallback"] } diff --git a/examples/custom_exception_rust/ext/ahriman/Cargo.lock b/examples/custom_exception_rust/ext/ahriman/Cargo.lock index af0c6a6d..e5a2a1b1 100644 --- a/examples/custom_exception_rust/ext/ahriman/Cargo.lock +++ b/examples/custom_exception_rust/ext/ahriman/Cargo.lock @@ -16,33 +16,34 @@ name = "ahriman" version = "0.1.0" dependencies = [ "magnus", + "rb-sys", ] [[package]] name = "bindgen" -version = "0.62.0" +version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6720a8b7b2d39dd533285ed438d458f65b31b5c257e6ac7bb3d7e82844dd722" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ "bitflags", "cexpr", "clang-sys", + "itertools", "lazy_static", "lazycell", - "peeking_take_while", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 1.0.109", + "syn", ] [[package]] name = "bitflags" -version = "1.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "cexpr" @@ -70,12 +71,27 @@ dependencies = [ "libloading", ] +[[package]] +name = "either" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" + [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -106,7 +122,7 @@ dependencies = [ [[package]] name = "magnus" -version = "0.6.0" +version = "0.7.0" dependencies = [ "magnus-macros", "rb-sys", @@ -120,7 +136,7 @@ version = "0.6.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn", ] [[package]] @@ -145,12 +161,6 @@ dependencies = [ "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" @@ -171,18 +181,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.79" +version = "0.9.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "939fb78db3e4f26665c1d4c7b91ca66d3578335a19aba552d4a6445811d07072" +checksum = "05b780e6858b0b0eced1d55d0f097c024b77a37b41f83bd35341130f78e37c51" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.79" +version = "0.9.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335a95eb0420d52fa94ef12019df3c2c250c6b19cbb3c60bd05cb7e9c362072c" +checksum = "44957a3bc513dad1b0f20bdd0ee3b82e729a59da44086a6b40d8bc71958a6db8" dependencies = [ "bindgen", "lazy_static", @@ -190,7 +200,7 @@ dependencies = [ "quote", "regex", "shell-words", - "syn 1.0.109", + "syn", ] [[package]] @@ -252,17 +262,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.27" diff --git a/examples/custom_exception_rust/ext/ahriman/Cargo.toml b/examples/custom_exception_rust/ext/ahriman/Cargo.toml index 9b5b48f3..99a6eaf6 100644 --- a/examples/custom_exception_rust/ext/ahriman/Cargo.toml +++ b/examples/custom_exception_rust/ext/ahriman/Cargo.toml @@ -8,3 +8,6 @@ crate-type = ["cdylib"] [dependencies] magnus = { path = "../../../.." } +# enable rb-sys feature to test against Ruby head. This is only needed if you +# want to work with the unreleased, in-development, next version of Ruby +rb-sys = { version = "*", default-features = false, features = ["stable-api-compiled-fallback"] } diff --git a/examples/rust_blank/ext/rust_blank/Cargo.lock b/examples/rust_blank/ext/rust_blank/Cargo.lock index ec6ee357..e82a0ea2 100644 --- a/examples/rust_blank/ext/rust_blank/Cargo.lock +++ b/examples/rust_blank/ext/rust_blank/Cargo.lock @@ -13,29 +13,29 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.62.0" +version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6720a8b7b2d39dd533285ed438d458f65b31b5c257e6ac7bb3d7e82844dd722" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ "bitflags", "cexpr", "clang-sys", + "itertools", "lazy_static", "lazycell", - "peeking_take_while", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 1.0.109", + "syn", ] [[package]] name = "bitflags" -version = "1.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "cexpr" @@ -63,12 +63,27 @@ dependencies = [ "libloading", ] +[[package]] +name = "either" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" + [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -99,7 +114,7 @@ dependencies = [ [[package]] name = "magnus" -version = "0.6.0" +version = "0.7.0" dependencies = [ "magnus-macros", "rb-sys", @@ -113,7 +128,7 @@ version = "0.6.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn", ] [[package]] @@ -138,12 +153,6 @@ dependencies = [ "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" @@ -164,18 +173,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.79" +version = "0.9.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "939fb78db3e4f26665c1d4c7b91ca66d3578335a19aba552d4a6445811d07072" +checksum = "05b780e6858b0b0eced1d55d0f097c024b77a37b41f83bd35341130f78e37c51" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.79" +version = "0.9.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335a95eb0420d52fa94ef12019df3c2c250c6b19cbb3c60bd05cb7e9c362072c" +checksum = "44957a3bc513dad1b0f20bdd0ee3b82e729a59da44086a6b40d8bc71958a6db8" dependencies = [ "bindgen", "lazy_static", @@ -183,7 +192,7 @@ dependencies = [ "quote", "regex", "shell-words", - "syn 1.0.109", + "syn", ] [[package]] @@ -226,6 +235,7 @@ name = "rust_blank" version = "0.1.0" dependencies = [ "magnus", + "rb-sys", ] [[package]] @@ -252,17 +262,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.27" diff --git a/examples/rust_blank/ext/rust_blank/Cargo.toml b/examples/rust_blank/ext/rust_blank/Cargo.toml index 2c515353..657a5a86 100644 --- a/examples/rust_blank/ext/rust_blank/Cargo.toml +++ b/examples/rust_blank/ext/rust_blank/Cargo.toml @@ -8,3 +8,6 @@ crate-type = ["cdylib"] [dependencies] magnus = { path = "../../../.." } +# enable rb-sys feature to test against Ruby head. This is only needed if you +# want to work with the unreleased, in-development, next version of Ruby +rb-sys = { version = "*", default-features = false, features = ["stable-api-compiled-fallback"] } diff --git a/src/api.rs b/src/api.rs index a0e7dc2e..a464f596 100644 --- a/src/api.rs +++ b/src/api.rs @@ -99,6 +99,7 @@ impl RubyGvlState { /// * [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 @@ -107,8 +108,10 @@ impl RubyGvlState { /// 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 @@ -123,6 +126,8 @@ impl RubyGvlState { /// * [`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 ()>); diff --git a/src/binding.rs b/src/binding.rs deleted file mode 100644 index a135e08b..00000000 --- a/src/binding.rs +++ /dev/null @@ -1,289 +0,0 @@ -use std::fmt; - -#[cfg(any(ruby_lte_3_1, docsrs))] -use rb_sys::{rb_binding_new, VALUE}; - -use crate::{ - error::Error, - into_value::IntoValue, - object::Object, - r_string::IntoRString, - symbol::IntoSymbol, - try_convert::TryConvert, - value::{ - private::{self, ReprValue as _}, - NonZeroValue, ReprValue, Value, - }, - Ruby, -}; - -/// A Value known to be an instance of Binding. -/// -/// See the [`ReprValue`] and [`Object`] traits for additional methods -/// available on this type. -#[derive(Clone, Copy)] -#[repr(transparent)] -#[deprecated(since = "0.6.0", note = "Please use `Value` instead.")] -pub struct Binding(NonZeroValue); - -#[allow(deprecated)] -impl Binding { - /// Create a new `Binding` from the current Ruby execution context. - /// - /// # Panics - /// - /// Panics if called from a non-Ruby thread. - /// - /// # Examples - /// - /// ``` - /// # #![allow(deprecated)] - /// use magnus::Binding; - /// # let _cleanup = unsafe { magnus::embed::init() }; - /// - /// let binding = Binding::new(); - /// ``` - #[allow(clippy::new_without_default)] - #[cfg(any(ruby_lte_3_1, docsrs))] - #[cfg_attr(docsrs, doc(cfg(ruby_lte_3_1)))] - #[deprecated(since = "0.2.0", note = "this will no longer function as of Ruby 3.2")] - #[inline] - pub fn new() -> Self { - crate::error::protect(|| unsafe { Binding::from_rb_value_unchecked(rb_binding_new()) }) - .unwrap() - } - - #[cfg(any(ruby_lte_3_1, docsrs))] - #[inline] - pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { - Self(NonZeroValue::new_unchecked(Value::new(val))) - } - - /// Return `Some(Binding)` if `val` is a `Binding`, `None` otherwise. - #[deprecated(since = "0.6.0")] - #[inline] - pub fn from_value(val: Value) -> Option { - unsafe { - val.is_kind_of(Ruby::get_with(val).class_binding()) - .then(|| Self(NonZeroValue::new_unchecked(val))) - } - } - - /// Evaluate a string of Ruby code within the binding's context. - /// - /// # Examples - /// - /// ``` - /// # #![allow(deprecated)] - /// use magnus::{eval, Binding}; - /// # let _cleanup = unsafe { magnus::embed::init() }; - /// - /// let binding = eval::("binding").unwrap(); - /// assert_eq!(binding.eval::<_, i64>("1 + 2").unwrap(), 3); - /// ``` - #[deprecated( - since = "0.6.0", - note = "Please use `value.funcall(\"eval\", (s,))` instead." - )] - pub fn eval(self, s: T) -> Result - where - T: IntoRString, - U: TryConvert, - { - self.funcall("eval", (s.into_r_string_with(&Ruby::get_with(self)),)) - } - - /// Get the named local variable from the binding. - /// - /// Returns `Ok(T)` if the method returns without error and the return - /// value converts to a `T`, or returns `Err` if the local variable does - /// not exist or the conversion fails. - /// - /// # Examples - /// - /// ``` - /// # #![allow(deprecated)] - /// use magnus::{eval, Binding, Value}; - /// # let _cleanup = unsafe { magnus::embed::init() }; - /// - /// let binding = eval::("binding").unwrap(); - /// binding.local_variable_set("a", 1); - /// assert_eq!(binding.local_variable_get::<_, i64>("a").unwrap(), 1); - /// assert!(binding.local_variable_get::<_, Value>("b").is_err()); - /// ``` - #[deprecated( - since = "0.6.0", - note = "Please use `value.funcall(\"local_variable_get\", (name,))` instead." - )] - pub fn local_variable_get(self, name: N) -> Result - where - N: IntoSymbol, - T: TryConvert, - { - self.funcall( - "local_variable_get", - (name.into_symbol_with(&Ruby::get_with(self)),), - ) - } - - /// Set the named local variable in the binding. - /// - /// # Examples - /// - /// ``` - /// # #![allow(deprecated)] - /// use magnus::{eval, Binding}; - /// # let _cleanup = unsafe { magnus::embed::init() }; - /// - /// let binding = eval::("binding").unwrap(); - /// binding.local_variable_set("a", 1); - /// assert_eq!(binding.local_variable_get::<_, i64>("a").unwrap(), 1); - /// ``` - #[deprecated( - since = "0.6.0", - note = "Please use `value.funcall(\"local_variable_set\", (name, val))` instead." - )] - pub fn local_variable_set(self, name: N, val: T) - where - N: IntoSymbol, - T: IntoValue, - { - self.funcall::<_, _, Value>( - "local_variable_set", - (name.into_symbol_with(&Ruby::get_with(self)), val), - ) - .unwrap(); - } -} - -#[allow(deprecated)] -impl fmt::Display for Binding { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", unsafe { self.to_s_infallible() }) - } -} - -#[allow(deprecated)] -impl fmt::Debug for Binding { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.inspect()) - } -} - -#[allow(deprecated)] -impl IntoValue for Binding { - #[inline] - fn into_value_with(self, _: &Ruby) -> Value { - self.0.get() - } -} - -#[allow(deprecated)] -impl Object for Binding {} - -#[allow(deprecated)] -unsafe impl private::ReprValue for Binding {} - -#[allow(deprecated)] -impl ReprValue for Binding {} - -#[allow(deprecated)] -impl TryConvert for Binding { - 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 Binding", unsafe { - val.classname() - },), - ) - }) - } -} - -/// 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 and [`Binding`]. -/// -/// # 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)*); - }}; -} diff --git a/src/block.rs b/src/block.rs index 3f04b007..06e4a849 100644 --- a/src/block.rs +++ b/src/block.rs @@ -52,7 +52,7 @@ impl Ruby { /// use magnus::{prelude::*, rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { - /// let proc = ruby.proc_new(|args, _block| { + /// 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) @@ -66,7 +66,7 @@ impl Ruby { /// } /// # Ruby::init(example).unwrap() /// ``` - pub fn proc_new(&self, block: fn(&[Value], Option) -> R) -> Proc + pub fn proc_new(&self, block: fn(&Ruby, &[Value], Option) -> R) -> Proc where R: BlockReturn, { @@ -80,15 +80,14 @@ impl Ruby { where R: BlockReturn, { - let func = std::mem::transmute::) -> R>(callback_arg); + 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; - #[cfg(ruby_lt_2_7)] - let call_func: unsafe extern "C" fn() -> VALUE = unsafe { std::mem::transmute(call_func) }; unsafe { #[allow(clippy::fn_to_numeric_cast)] @@ -109,7 +108,7 @@ impl Ruby { /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let mut count = 0; /// - /// let proc = ruby.proc_from_fn(move |args, _block| { + /// let proc = ruby.proc_from_fn(move |_ruby, args, _block| { /// let step = i64::try_convert(*args.get(0).unwrap())?; /// count += step; /// Ok(count) @@ -125,7 +124,7 @@ impl Ruby { /// ``` pub fn proc_from_fn(&self, block: F) -> Proc where - F: 'static + Send + FnMut(&[Value], Option) -> R, + F: 'static + Send + FnMut(&Ruby, &[Value], Option) -> R, R: BlockReturn, { unsafe extern "C" fn call( @@ -136,7 +135,7 @@ impl Ruby { blockarg: VALUE, ) -> VALUE where - F: FnMut(&[Value], Option) -> R, + F: FnMut(&Ruby, &[Value], Option) -> R, R: BlockReturn, { let closure = &mut *(callback_arg as *mut F); @@ -148,8 +147,6 @@ impl Ruby { let (closure, keepalive) = wrap_closure(block); let call_func = call:: as unsafe extern "C" fn(VALUE, VALUE, c_int, *const VALUE, VALUE) -> VALUE; - #[cfg(ruby_lt_2_7)] - let call_func: unsafe extern "C" fn() -> VALUE = unsafe { std::mem::transmute(call_func) }; let proc = unsafe { Proc::from_rb_value_unchecked(rb_proc_new(Some(call_func), closure as VALUE)) @@ -212,10 +209,11 @@ impl Proc { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{block::Proc, prelude::*, rb_assert}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// - /// let proc = Proc::new(|args, _block| { + /// 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) @@ -226,11 +224,12 @@ impl Proc { /// rb_assert!("[1, 2, 3, 4, 5].inject(&proc) == 15", proc); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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(&[Value], Option) -> R) -> Self + pub fn new(block: fn(&Ruby, &[Value], Option) -> R) -> Self where R: BlockReturn, { @@ -250,12 +249,13 @@ impl Proc { /// # 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 |args, _block| { + /// let proc = Proc::from_fn(move |_ruby, args, _block| { /// let step = i64::try_convert(*args.get(0).unwrap())?; /// count += step; /// Ok(count) @@ -266,13 +266,14 @@ impl Proc { /// rb_assert!("proc.call(2) == 4", proc); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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(&[Value], Option) -> R, + F: 'static + Send + FnMut(&Ruby, &[Value], Option) -> R, R: BlockReturn, { get_ruby!().proc_from_fn(block) @@ -287,56 +288,68 @@ impl Proc { /// # Examples /// /// ``` - /// use magnus::{block::Proc, eval, Integer, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{block::Proc, Error, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let proc: Proc = ruby.eval("Proc.new {|a, b| a + b}").unwrap(); /// - /// let proc: Proc = 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 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 a slice - /// let result: i64 = proc - /// .call(&[Integer::from_i64(3), 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 an array - /// let result: i64 = proc - /// .call([Integer::from_i64(5), 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); /// - /// // call with a Ruby array - /// let array = RArray::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, eval, kwargs}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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 proc: Proc = 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); /// - /// 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, eval, rb_assert, Value}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{block::Proc, rb_assert, Error, Ruby, Value}; /// - /// let proc: Proc = eval("Proc.new { $called = true }").unwrap(); + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let proc: Proc = ruby.eval("Proc.new { $called = true }").unwrap(); /// - /// let _: Value = proc.call(()).unwrap(); + /// let _: Value = proc.call(()).unwrap(); /// - /// rb_assert!("$called == true"); + /// rb_assert!(ruby, "$called == true"); + /// + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn call(self, args: A) -> Result where @@ -472,7 +485,7 @@ impl TryConvert for Proc { /// dropped when the returned `Value` is garbage collected. fn wrap_closure(func: F) -> (*mut F, Value) where - F: FnMut(&[Value], Option) -> R, + F: FnMut(&Ruby, &[Value], Option) -> R, R: BlockReturn, { struct Closure(F, DataType); @@ -746,6 +759,7 @@ impl Ruby { /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// use magnus::{block::block_given, define_global_function, function, rb_assert}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -759,9 +773,10 @@ impl Ruby { /// rb_assert!("got_block? == false"); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -777,6 +792,7 @@ pub fn block_given() -> bool { /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// use magnus::{ /// block::{block_proc, Proc}, /// define_global_function, function, rb_assert, Error, @@ -792,9 +808,10 @@ pub fn block_given() -> bool { /// rb_assert!("make_proc {}.is_a?(Proc)"); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -815,6 +832,7 @@ pub fn block_proc() -> Result { /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// use magnus::{ /// block::yield_value, define_global_function, function, rb_assert, Error, RArray, Value, /// }; @@ -837,9 +855,10 @@ pub fn block_proc() -> Result { /// rb_assert!(r#"vars == ["foo", "bar", "baz"]"#, vars); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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 @@ -864,6 +883,7 @@ where /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// use magnus::{ /// block::yield_values, define_global_function, function, rb_assert, Error, RArray, Value, /// }; @@ -889,9 +909,10 @@ where /// rb_assert!(r#"vars == [[0, "foo"], [1, "bar"], [2, "baz"]]"#, vars); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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 @@ -916,6 +937,7 @@ where /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// use magnus::{ /// block::yield_splat, define_global_function, function, rb_assert, Error, RArray, Value, /// }; @@ -950,9 +972,10 @@ where /// rb_assert!(r#"vars == [[0, "foo"], [1, "bar"], [2, "baz"]]"#, vars); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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 @@ -1053,34 +1076,33 @@ where /// # Examples /// /// ``` -/// use magnus::{ -/// block::{block_given, Yield}, -/// define_global_function, eval, method, -/// prelude::*, -/// rb_assert, RArray, Value, -/// }; -/// # let _cleanup = unsafe { magnus::embed::init() }; +/// use magnus::{block::Yield, method, prelude::*, rb_assert, Error, Ruby, Value}; /// -/// fn count_to_3(rb_self: Value) -> Yield> { -/// if block_given() { +/// 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", ())) /// } /// } /// -/// define_global_function("count_to_3", method!(count_to_3, 0)); +/// 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 = RArray::new(); -/// rb_assert!("count_to_3 {|i| a << i} == nil", a); -/// rb_assert!("a == [1, 2, 3]", a); +/// // 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 = eval("count_to_3").unwrap(); +/// // call Ruby method without a block. +/// let enumerator: Value = ruby.eval("count_to_3").unwrap(); /// -/// rb_assert!("enumerator.next == 1", enumerator); -/// rb_assert!("enumerator.next == 2", enumerator); +/// 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. @@ -1097,34 +1119,36 @@ pub enum Yield { /// # Examples /// /// ``` -/// use magnus::{ -/// block::{block_given, YieldValues}, -/// define_global_function, eval, method, -/// prelude::*, -/// rb_assert, RArray, Value, -/// }; -/// # let _cleanup = unsafe { magnus::embed::init() }; +/// use magnus::{block::YieldValues, method, prelude::*, rb_assert, Error, Ruby, Value}; /// -/// fn count_to_3_abc(rb_self: Value) -> YieldValues> { -/// if block_given() { +/// 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", ())) /// } /// } /// -/// define_global_function("count_to_3_abc", method!(count_to_3_abc, 0)); +/// 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 = RArray::new(); -/// rb_assert!("count_to_3_abc {|i, c| a << [i, c]} == nil", a); -/// rb_assert!(r#"a == [[1, "a"], [2, "b"], [3, "c"]]"#, a); +/// // 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 = eval("count_to_3_abc").unwrap(); +/// // call Ruby method without a block. +/// let enumerator: Value = ruby.eval("count_to_3_abc").unwrap(); /// -/// rb_assert!(r#"enumerator.next == [1, "a"]"#, enumerator); -/// rb_assert!(r#"enumerator.next == [2, "b"]"#, enumerator); +/// 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. @@ -1141,18 +1165,15 @@ pub enum YieldValues { /// # Examples /// /// ``` -/// use magnus::{ -/// block::{block_given, YieldSplat}, -/// define_global_function, eval, method, -/// prelude::*, -/// rb_assert, RArray, Value, -/// }; -/// # let _cleanup = unsafe { magnus::embed::init() }; +/// use magnus::{block::YieldSplat, method, prelude::*, rb_assert, Error, RArray, Ruby, Value}; /// -/// fn count_to_3_abc(rb_self: Value) -> YieldSplat> { -/// if block_given() { +/// 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)| { -/// let ary = RArray::new(); +/// // 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 @@ -1162,18 +1183,23 @@ pub enum YieldValues { /// } /// } /// -/// define_global_function("count_to_3_abc", method!(count_to_3_abc, 0)); +/// 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 with a block. -/// let a = RArray::new(); -/// rb_assert!("count_to_3_abc {|i, c| a << [i, c]} == nil", a); -/// rb_assert!(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(); /// -/// // call Ruby method without a block. -/// let enumerator: Value = eval("count_to_3_abc").unwrap(); +/// rb_assert!(ruby, r#"enumerator.next == [1, "a"]"#, enumerator); +/// rb_assert!(ruby, r#"enumerator.next == [2, "b"]"#, enumerator); /// -/// rb_assert!(r#"enumerator.next == [1, "a"]"#, enumerator); -/// rb_assert!(r#"enumerator.next == [2, "b"]"#, enumerator); +/// Ok(()) +/// } +/// # Ruby::init(example).unwrap() /// ``` pub enum YieldSplat { /// Yields `I::Item` to given block. diff --git a/src/class.rs b/src/class.rs index 4937c959..1e380664 100644 --- a/src/class.rs +++ b/src/class.rs @@ -120,18 +120,26 @@ pub trait Class: Module { /// # Examples /// /// ``` - /// use magnus::{class, prelude::*, RClass}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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())); /// - /// let class = RClass::new(class::object()).unwrap(); - /// assert!(class.is_kind_of(class::class())); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` /// /// ``` - /// use magnus::{exception, prelude::*, ExceptionClass}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, ExceptionClass, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// assert!(ExceptionClass::new(ruby.exception_standard_error()).is_ok()); /// - /// assert!(ExceptionClass::new(exception::standard_error()).is_ok()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn new(superclass: Self) -> Result; @@ -141,70 +149,85 @@ pub trait Class: Module { /// # Examples /// /// ``` - /// use magnus::{class, prelude::*}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, Ruby}; /// - /// let s = class::string().new_instance(()).unwrap(); - /// assert!(s.is_kind_of(class::string())); - /// assert_eq!(s.to_string(), ""); - /// ``` + /// 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::*, RClass}; - /// # let _cleanup = unsafe { magnus::embed::init() }; /// - /// let cls: RClass = eval!( - /// r#" - /// class Foo - /// def initialize(bar, baz:) - /// @bar = bar - /// @baz = baz - /// end + /// ``` + /// use magnus::{eval, kwargs, prelude::*, Error, RClass, Ruby}; /// - /// attr_reader(:bar, :baz) - /// end + /// 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); /// - /// Object.const_get(:Foo) - /// "# - /// ) - /// .unwrap(); - /// let instance = cls.new_instance((1, kwargs!("baz" => 2))).unwrap(); - /// assert!(instance.is_kind_of(cls)); - /// let bar: i32 = instance.funcall("bar", ()).unwrap(); - /// assert_eq!(bar, 1); - /// let baz: i32 = instance.funcall("baz", ()).unwrap(); - /// assert_eq!(baz, 2); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` /// /// ``` - /// use magnus::{exception, prelude::*}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, Ruby}; /// - /// let s = exception::standard_error() - /// .new_instance(("bang!",)) - /// .unwrap(); - /// assert!(s.is_kind_of(exception::standard_error())); + /// 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::*}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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!"); /// - /// let exc: ExceptionClass = eval!( - /// r#" - /// class MyError < StandardError - /// def initialize(message:) - /// super(message) - /// end - /// end - /// - /// Object.const_get(:MyError) - /// "# - /// ).unwrap(); - /// let s = exc.new_instance((kwargs!("message" => "bang!"),)).unwrap(); - /// assert!(s.is_kind_of(exc)); - /// let message: String = s.funcall("message", ()).unwrap(); - /// assert_eq!(message, "bang!"); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn new_instance(self, args: T) -> Result where @@ -216,19 +239,27 @@ pub trait Class: Module { /// # Examples /// /// ``` - /// use magnus::{class, prelude::*}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, Ruby}; /// - /// let s = class::string().obj_alloc().unwrap(); - /// assert!(s.is_kind_of(class::string())); + /// 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::{exception, prelude::*}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, Ruby}; /// - /// let s = exception::standard_error().obj_alloc().unwrap(); - /// assert!(s.is_kind_of(exception::standard_error())); + /// 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; @@ -239,19 +270,27 @@ pub trait Class: Module { /// # Examples /// /// ``` - /// use magnus::{class, prelude::*}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, Ruby}; /// - /// let klass = class::hash().superclass().unwrap(); - /// assert!(klass.equal(class::object()).unwrap()); + /// 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::{class, exception, prelude::*}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let klass = ruby.exception_exception().superclass()?; + /// assert!(klass.equal(ruby.class_object())?); /// - /// let klass = exception::exception().superclass().unwrap(); - /// assert!(klass.equal(class::object()).unwrap()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn superclass(self) -> Result { protect(|| unsafe { @@ -272,23 +311,31 @@ pub trait Class: Module { /// # Examples /// /// ``` - /// use magnus::{class, prelude::*}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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"); /// - /// let value = 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::{exception, prelude::*}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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"); /// - /// let value = 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()); @@ -331,10 +378,7 @@ pub trait Class: Module { /// ``` /// use std::cell::RefCell; /// - /// use magnus::{ - /// class, define_class, embed, eval, function, method, prelude::*, wrap, Error, RClass, Value, - /// }; - /// # let _cleanup = unsafe { embed::init() }; + /// use magnus::{function, method, prelude::*, wrap, Error, RClass, Ruby, Value}; /// /// #[derive(Default)] /// struct Point { @@ -370,34 +414,32 @@ pub trait Class: Module { /// } /// } /// - /// let class = define_class("Point", class::object()).unwrap(); - /// class.define_alloc_func::(); - /// class - /// .define_singleton_method("create", function!(MutPoint::create, 2)) - /// .unwrap(); - /// class - /// .define_singleton_method("call_new", method!(MutPoint::call_new, 2)) - /// .unwrap(); - /// class - /// .define_method("initialize", method!(MutPoint::initialize, 2)) - /// .unwrap(); - /// class - /// .define_method("distance", method!(MutPoint::distance, 1)) - /// .unwrap(); - /// - /// let d: f64 = 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)", - /// ) - /// .unwrap(); - /// - /// assert_eq!(d, 5.66); + /// 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 @@ -424,15 +466,6 @@ pub trait Class: Module { } } - #[doc(hidden)] - #[deprecated( - since = "0.6.0", - note = "please use `undef_default_alloc_func` instead" - )] - fn undef_alloc_func(self) { - unsafe { rb_undef_alloc_func(self.as_rb_value()) } - } - /// Remove the allocator function of a class if it is Ruby's default /// allocator function. /// @@ -444,17 +477,22 @@ pub trait Class: Module { /// # Examples /// /// ``` - /// use magnus::{class, Class}; - /// # let _cleanup = unsafe { magnus::embed::init() }; - /// let class = magnus::define_class("Point", class::object()).unwrap(); + /// 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(); + /// class.undef_default_alloc_func(); /// - /// let instance = class.new_instance(()); - /// assert_eq!( - /// "allocator undefined for Point", - /// instance.err().unwrap().to_string() - /// ); + /// 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(); @@ -1174,9 +1212,10 @@ impl Ruby { /// Panics if called from a non-Ruby thread. See [`Ruby::class_array`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1189,9 +1228,10 @@ pub fn array() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_basic_object`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1204,9 +1244,10 @@ pub fn basic_object() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_binding`] for /// the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1219,9 +1260,10 @@ pub fn binding() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_class`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1234,9 +1276,10 @@ pub fn class() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_complex`] for /// the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1249,9 +1292,10 @@ pub fn complex() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_dir`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1264,9 +1308,10 @@ pub fn dir() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_encoding`] for /// the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1279,9 +1324,10 @@ pub fn encoding() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_enumerator`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1294,9 +1340,10 @@ pub fn enumerator() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_false_class`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1309,9 +1356,10 @@ pub fn false_class() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_file`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1324,9 +1372,10 @@ pub fn file() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_float`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1339,9 +1388,10 @@ pub fn float() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_hash`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1354,9 +1404,10 @@ pub fn hash() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_io`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1369,9 +1420,10 @@ pub fn io() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_integer`] for /// the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1384,9 +1436,10 @@ pub fn integer() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_match`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1399,9 +1452,10 @@ pub fn match_class() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_method`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1414,9 +1468,10 @@ pub fn method() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_module`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1429,9 +1484,10 @@ pub fn module() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_name_error_mesg`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1444,9 +1500,10 @@ pub fn name_error_mesg() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_nil_class`] for /// the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1459,9 +1516,10 @@ pub fn nil_class() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_numeric`] for /// the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1474,9 +1532,10 @@ pub fn numeric() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_object`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1489,9 +1548,10 @@ pub fn object() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_proc`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1504,9 +1564,10 @@ pub fn proc() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_random`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1519,9 +1580,10 @@ pub fn random() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_range`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1534,9 +1596,10 @@ pub fn range() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_rational`] for /// the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1549,9 +1612,10 @@ pub fn rational() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_refinement`] for /// the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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] @@ -1566,9 +1630,10 @@ pub fn refinement() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_regexp`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1581,9 +1646,10 @@ pub fn regexp() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_stat`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1596,9 +1662,10 @@ pub fn stat() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_string`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1611,9 +1678,10 @@ pub fn string() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_struct`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1626,9 +1694,10 @@ pub fn struct_class() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_symbol`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1641,9 +1710,10 @@ pub fn symbol() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_thread`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1656,9 +1726,10 @@ pub fn thread() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_time`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1671,9 +1742,10 @@ pub fn time() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_true_class`] for /// the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1686,9 +1758,10 @@ pub fn true_class() -> RClass { /// Panics if called from a non-Ruby thread. See [`Ruby::class_unbound_method`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() diff --git a/src/embed.rs b/src/embed.rs index 037373f8..caba1e10 100644 --- a/src/embed.rs +++ b/src/embed.rs @@ -11,7 +11,8 @@ use std::{ #[cfg(windows)] use rb_sys::rb_w32_sysinit; use rb_sys::{ - ruby_cleanup, ruby_exec_node, ruby_process_options, ruby_set_script_name, ruby_setup, + ruby_cleanup, ruby_exec_node, ruby_init_stack, ruby_process_options, ruby_set_script_name, + ruby_setup, VALUE, }; use crate::{ @@ -75,6 +76,10 @@ impl Deref for Cleanup { #[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)] @@ -206,9 +211,10 @@ impl Ruby { /// Panics if called from a non-Ruby thread. See [`Ruby::script`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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 diff --git a/src/encoding.rs b/src/encoding.rs index 96762096..3478f39d 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -113,9 +113,10 @@ impl Encoding { /// Panics if called from a non-Ruby thread. See /// [`Ruby::enc_default_external`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -131,9 +132,10 @@ impl Encoding { /// Panics if called from a non-Ruby thread. See /// [`Ruby::enc_default_internal`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -323,9 +325,10 @@ impl RbEncoding { /// Panics if called from a non-Ruby thread. See /// [`Ruby::ascii8bit_encoding`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -338,9 +341,10 @@ impl RbEncoding { /// Panics if called from a non-Ruby thread. See [`Ruby::utf8_encoding`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -353,9 +357,10 @@ impl RbEncoding { /// Panics if called from a non-Ruby thread. See [`Ruby::usascii_encoding`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -371,9 +376,10 @@ impl RbEncoding { /// Panics if called from a non-Ruby thread. See [`Ruby::locale_encoding`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -389,9 +395,10 @@ impl RbEncoding { /// Panics if called from a non-Ruby thread. See /// [`Ruby::filesystem_encoding`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -407,9 +414,10 @@ impl RbEncoding { /// Panics if called from a non-Ruby thread. See /// [`Ruby::default_external_encoding`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -425,9 +433,10 @@ impl RbEncoding { /// Panics if called from a non-Ruby thread. See /// [`Ruby::default_internal_encoding`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -443,6 +452,7 @@ impl RbEncoding { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::encoding::RbEncoding; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -450,9 +460,10 @@ impl RbEncoding { /// assert_eq!(RbEncoding::find("BINARY").unwrap().name(), "ASCII-8BIT"); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -467,11 +478,15 @@ impl RbEncoding { /// # Examples /// /// ``` - /// use magnus::encoding::RbEncoding; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// assert_eq!(RbEncoding::utf8().name(), "UTF-8"); - /// assert_eq!(RbEncoding::find("UTF-16").unwrap().name(), "UTF-16"); + /// 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 @@ -488,11 +503,15 @@ impl RbEncoding { /// # Examples /// /// ``` - /// use magnus::encoding::RbEncoding; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// assert_eq!(ruby.usascii_encoding().mbminlen(), 1); + /// assert_eq!(ruby.utf8_encoding().mbminlen(), 1); /// - /// assert_eq!(RbEncoding::usascii().mbminlen(), 1); - /// assert_eq!(RbEncoding::utf8().mbminlen(), 1); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn mbminlen(&self) -> usize { unsafe { self.0.as_ref().min_enc_len as usize } @@ -504,11 +523,15 @@ impl RbEncoding { /// # Examples /// /// ``` - /// use magnus::encoding::RbEncoding; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// assert_eq!(ruby.usascii_encoding().mbmaxlen(), 1); + /// assert_eq!(ruby.utf8_encoding().mbmaxlen(), 4); /// - /// assert_eq!(RbEncoding::usascii().mbmaxlen(), 1); - /// assert_eq!(RbEncoding::utf8().mbmaxlen(), 4); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn mbmaxlen(&self) -> usize { unsafe { self.0.as_ref().max_enc_len as usize } @@ -530,26 +553,30 @@ impl RbEncoding { /// ``` /// use magnus::{ /// encoding::{EncodingCapable, RbEncoding}, - /// RString, + /// Error, Ruby, /// }; - /// # let _cleanup = unsafe { magnus::embed::init() }; /// - /// let s = RString::new("🦀 café"); - /// let encoding: RbEncoding = s.enc_get().into(); - /// let mut chars = 0; + /// 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..]; + /// } + /// } /// - /// unsafe { - /// let mut bytes = s.as_slice(); - /// assert_eq!(bytes.len(), 10); + /// assert_eq!(chars, 6); /// - /// while !bytes.is_empty() { - /// chars += 1; - /// let len = encoding.mbclen(bytes); - /// bytes = &bytes[len..]; - /// } + /// Ok(()) /// } - /// - /// assert_eq!(chars, 6); + /// # Ruby::init(example).unwrap() /// ``` pub fn mbclen(&self, slice: &[u8]) -> usize { let Range { start: p, end: e } = slice.as_ptr_range(); @@ -574,26 +601,30 @@ impl RbEncoding { /// ``` /// use magnus::{ /// encoding::{EncodingCapable, RbEncoding}, - /// RString, + /// Error, Ruby, /// }; - /// # let _cleanup = unsafe { magnus::embed::init() }; /// - /// let s = RString::new("🦀 café"); - /// let encoding: RbEncoding = s.enc_get().into(); - /// let mut chars = 0; + /// 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..]; + /// } + /// } /// - /// unsafe { - /// let mut bytes = s.as_slice(); - /// assert_eq!(bytes.len(), 10); + /// assert_eq!(chars, 6); /// - /// while !bytes.is_empty() { - /// chars += 1; - /// let len = encoding.fast_mbclen(bytes); - /// bytes = &bytes[len..]; - /// } + /// Ok(()) /// } - /// - /// assert_eq!(chars, 6); + /// # Ruby::init(example).unwrap() /// ``` pub fn fast_mbclen(&self, slice: &[u8]) -> usize { let Range { start: p, end: e } = slice.as_ptr_range(); @@ -612,29 +643,33 @@ impl RbEncoding { /// ``` /// use magnus::{ /// encoding::{EncodingCapable, MbcLen, RbEncoding}, - /// RString, + /// Error, Ruby, /// }; - /// # let _cleanup = unsafe { magnus::embed::init() }; - /// - /// let s = RString::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"), + /// 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); + /// 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(); @@ -662,29 +697,33 @@ impl RbEncoding { /// ``` /// use magnus::{ /// encoding::{EncodingCapable, RbEncoding}, - /// RString, + /// Error, Ruby, /// }; - /// # let _cleanup = unsafe { magnus::embed::init() }; - /// - /// let s = RString::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..]; + /// 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"), /// } - /// None => panic!("string not ASCII"), /// } /// } - /// } /// - /// assert_eq!(chars, [101, 120, 97, 109, 112, 108, 101]); + /// 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(); @@ -711,25 +750,29 @@ impl RbEncoding { /// ``` /// use magnus::{ /// encoding::{EncodingCapable, RbEncoding}, - /// RString, + /// Error, Ruby, /// }; - /// # let _cleanup = unsafe { magnus::embed::init() }; /// - /// let s = RString::new("🦀 café"); - /// let encoding: RbEncoding = s.enc_get().into(); - /// let mut codepoints = Vec::new(); + /// 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(); + /// unsafe { + /// let mut bytes = s.as_slice(); /// - /// while !bytes.is_empty() { - /// let (codepoint, len) = encoding.codepoint_len(bytes).unwrap(); - /// codepoints.push(codepoint); - /// bytes = &bytes[len..]; + /// 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]); + /// 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(); @@ -753,11 +796,15 @@ impl RbEncoding { /// # Examples /// /// ``` - /// use magnus::encoding::RbEncoding; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// assert_eq!(RbEncoding::utf8().codelen(97).unwrap(), 1); - /// assert_eq!(RbEncoding::utf8().codelen(129408).unwrap(), 4); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn codelen(&self, code: u32) -> Result { let handle = unsafe { Ruby::get_unchecked() }; @@ -780,21 +827,29 @@ impl RbEncoding { /// # Examples /// /// ``` - /// use magnus::{encoding::RbEncoding, eval}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{eval, Error, Ruby}; /// - /// let c = RbEncoding::usascii().chr(97).unwrap(); - /// let res: bool = eval!(r#"c == "a""#, c).unwrap(); - /// assert!(res); + /// 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::{encoding::RbEncoding, eval}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// let c = RbEncoding::utf8().chr(129408).unwrap(); - /// let res: bool = eval!(r#"c == "🦀""#, c).unwrap(); - /// assert!(res); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn chr(&self, code: u32) -> Result { protect(|| unsafe { @@ -808,11 +863,15 @@ impl RbEncoding { /// # Examples /// /// ``` - /// use magnus::encoding::RbEncoding; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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])); /// - /// assert!(RbEncoding::utf8().is_mbc_newline(&[10])); - /// assert!(!RbEncoding::utf8().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(); @@ -828,14 +887,18 @@ impl RbEncoding { /// # Examples /// /// ``` - /// use magnus::encoding::{CType, RbEncoding}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{encoding::CType, Error, Ruby}; /// - /// assert!(RbEncoding::utf8().is_code_ctype(9, CType::Space)); // "\t" - /// assert!(RbEncoding::utf8().is_code_ctype(32, CType::Space)); // " " - /// assert!(!RbEncoding::utf8().is_code_ctype(65, CType::Space)); // "A" - /// assert!(RbEncoding::utf8().is_code_ctype(65, CType::Alnum)); // "A" - /// assert!(RbEncoding::utf8().is_code_ctype(65, CType::Upper)); // "A" + /// 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 } @@ -1006,9 +1069,10 @@ impl Index { /// Panics if called from a non-Ruby thread. See /// [`Ruby::ascii8bit_encindex`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1021,9 +1085,10 @@ impl Index { /// Panics if called from a non-Ruby thread. See [`Ruby::utf8_encindex`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1036,9 +1101,10 @@ impl Index { /// Panics if called from a non-Ruby thread. See [`Ruby::usascii_encindex`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1054,9 +1120,10 @@ impl Index { /// Panics if called from a non-Ruby thread. See [`Ruby::locale_encindex`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1072,9 +1139,10 @@ impl Index { /// Panics if called from a non-Ruby thread. See /// [`Ruby::filesystem_encindex`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1090,6 +1158,7 @@ impl Index { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::encoding; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -1098,9 +1167,10 @@ impl Index { /// assert!(encoding::Index::find("none").is_err()); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -1153,13 +1223,14 @@ pub trait EncodingCapable: ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{ - /// encoding::{self, EncodingCapable}, - /// RString, - /// }; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{encoding::EncodingCapable, Error, Ruby}; /// - /// assert!(RString::new("example").enc_get() == encoding::Index::utf8()); + /// 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()) }; @@ -1178,16 +1249,17 @@ pub trait EncodingCapable: ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{ - /// encoding::{self, EncodingCapable}, - /// RString, - /// }; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{encoding::EncodingCapable, Error, Ruby}; /// - /// let s = RString::new("example"); - /// assert!(s.enc_get() == encoding::Index::utf8()); - /// s.enc_set(encoding::Index::usascii()).unwrap(); - /// assert!(s.enc_get() == encoding::Index::usascii()); + /// 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 @@ -1213,16 +1285,17 @@ pub trait EncodingCapable: ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{ - /// encoding::{self, EncodingCapable}, - /// RString, - /// }; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{encoding::EncodingCapable, Error, Ruby}; /// - /// let s = RString::new("example"); - /// assert!(s.enc_get() == encoding::Index::utf8()); - /// s.enc_associate(encoding::Index::usascii()).unwrap(); - /// assert!(s.enc_get() == encoding::Index::usascii()); + /// 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 @@ -1244,16 +1317,20 @@ pub trait EncodingCapable: ReprValue + Copy { /// # Examples /// /// ``` -/// use magnus::{encoding, prelude::*, RString}; -/// # let _cleanup = unsafe { magnus::embed::init() }; +/// use magnus::{encoding, prelude::*, Error, Ruby}; +/// +/// fn example(ruby: &Ruby) -> Result<(), Error> { +/// let a = ruby.str_new("a"); +/// let b = ruby.str_new("b"); /// -/// let a = RString::new("a"); -/// let b = RString::new("b"); +/// assert!(a.enc_get() == ruby.utf8_encindex()); +/// b.enc_set(ruby.usascii_encindex())?; /// -/// assert!(a.enc_get() == encoding::Index::utf8()); -/// b.enc_set(encoding::Index::usascii()).unwrap(); +/// assert_eq!(encoding::compatible(a, b).unwrap().name(), "UTF-8"); /// -/// assert_eq!(encoding::compatible(a, b).unwrap().name(), "UTF-8"); +/// Ok(()) +/// } +/// # Ruby::init(example).unwrap() /// ``` pub fn compatible(v1: T, v2: U) -> Option where @@ -1272,16 +1349,20 @@ where /// # Examples /// /// ``` -/// use magnus::{encoding, prelude::*, RString}; -/// # let _cleanup = unsafe { magnus::embed::init() }; +/// use magnus::{encoding, prelude::*, Error, Ruby}; /// -/// let a = RString::new("a"); -/// let b = RString::new("b"); +/// fn example(ruby: &Ruby) -> Result<(), Error> { +/// let a = ruby.str_new("a"); +/// let b = ruby.str_new("b"); /// -/// assert!(a.enc_get() == encoding::Index::utf8()); -/// b.enc_set(encoding::Index::usascii()).unwrap(); +/// assert!(a.enc_get() == ruby.utf8_encindex()); +/// b.enc_set(ruby.usascii_encindex())?; /// -/// assert_eq!(encoding::check(a, b).unwrap().name(), "UTF-8"); +/// assert_eq!(encoding::check(a, b)?.name(), "UTF-8"); +/// +/// Ok(()) +/// } +/// # Ruby::init(example).unwrap() /// ``` pub fn check(v1: T, v2: U) -> Result where @@ -1306,18 +1387,22 @@ where /// # Examples /// /// ``` -/// use magnus::{encoding, prelude::*, RString}; -/// # let _cleanup = unsafe { magnus::embed::init() }; +/// 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()); /// -/// let a = RString::new("a"); -/// assert!(a.enc_get() == encoding::Index::utf8()); -/// let b = RString::new("b"); -/// assert!(b.enc_get() == encoding::Index::utf8()); +/// a.enc_set(ruby.usascii_encindex())?; +/// encoding::copy(b, a)?; /// -/// a.enc_set(encoding::Index::usascii()).unwrap(); -/// encoding::copy(b, a).unwrap(); +/// assert!(b.enc_get() == ruby.usascii_encindex()); /// -/// assert!(b.enc_get() == encoding::Index::usascii()); +/// Ok(()) +/// } +/// # Ruby::init(example).unwrap() /// ``` pub fn copy(dst: T, src: U) -> Result<(), Error> where diff --git a/src/enumerator.rs b/src/enumerator.rs index 3f4b6de6..05fea39e 100644 --- a/src/enumerator.rs +++ b/src/enumerator.rs @@ -27,17 +27,21 @@ use crate::{ /// # Examples /// /// ``` -/// use magnus::{prelude::*, rb_assert, RArray, RString}; -/// # let _cleanup = unsafe { magnus::embed::init() }; +/// use magnus::{prelude::*, rb_assert, Error, Ruby}; /// -/// let s = RString::new("foo\nbar\nbaz"); -/// let results = RArray::new(); +/// 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.unwrap()).unwrap(); +/// // `enumeratorize` returns `Enumerator` +/// for line in s.enumeratorize("each_line", ()) { +/// results.push(line?)?; +/// } +/// rb_assert!(r#"results == ["foo\n", "bar\n", "baz"]"#, results); +/// +/// Ok(()) /// } -/// rb_assert!(r#"results == ["foo\n", "bar\n", "baz"]"#, results); +/// # Ruby::init(example).unwrap() /// ``` #[derive(Clone, Copy)] #[repr(transparent)] diff --git a/src/error.rs b/src/error.rs index 4c2ddffb..9b1afd21 100644 --- a/src/error.rs +++ b/src/error.rs @@ -53,22 +53,16 @@ impl Ruby { /// use magnus::{prelude::*, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { - /// let i: i64 = ruby - /// .range_new(1, 100, false)? - /// .block_call("each", (), |args, _block| { - /// let i = i64::try_convert(*args.get(0).unwrap())?; - /// if i % 3 == 0 && i % 5 == 0 { - /// // `block_call` takes a function pointer, not a - /// // closure, so can't capture `ruby`. As we know this - /// // will always be called from Ruby it's safe to get - /// // `Ruby` without the checks that we're on a Ruby - /// // thread. - /// let ruby = unsafe { Ruby::get_unchecked() }; - /// Err(ruby.iter_break_value(i)) - /// } else { - /// Ok(()) - /// } - /// })?; + /// 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(()) @@ -100,7 +94,7 @@ impl Ruby { pub type Result = std::result::Result; /// The possible types of [`Error`]. -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ErrorType { /// An interrupt, such as `break` or `throw`. Jump(Tag), @@ -112,7 +106,7 @@ pub enum ErrorType { } /// Wrapper type for Ruby `Exception`s or other interrupts. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Error(ErrorType); impl Error { @@ -122,28 +116,32 @@ impl Error { /// # Examples /// /// ``` - /// use magnus::{define_global_function, eval, exception, function, prelude::*, Error, Exception}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{function, prelude::*, Error, Exception, Ruby}; /// - /// fn bang() -> Result<(), Error> { - /// Err(Error::new(exception::runtime_error(), "BANG")) + /// fn bang(ruby: &Ruby) -> Result<(), Error> { + /// Err(Error::new(ruby.exception_runtime_error(), "BANG")) /// } - /// define_global_function("bang", function!(bang, 0)); - /// - /// let error: Exception = eval( - /// " - /// begin - /// bang - /// rescue => e - /// e - /// end - /// ", - /// ) - /// .unwrap(); /// - /// assert!(error.is_kind_of(exception::runtime_error())); - /// let msg: String = error.funcall("message", ()).unwrap(); - /// assert_eq!(msg, "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 @@ -166,12 +164,13 @@ impl Error { /// # 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", (), |args, _block| { + /// .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)) @@ -184,9 +183,10 @@ impl Error { /// assert_eq!(i, 15); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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 @@ -201,13 +201,12 @@ impl Error { /// # Examples /// /// ``` - /// use magnus::{ - /// class, eval, exception::ExceptionClass, prelude::*, Error, RModule, TryConvert, Value, - /// }; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{exception::ExceptionClass, prelude::*, Error, RModule, Ruby, TryConvert, Value}; /// - /// let err: Error = eval::( - /// " + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let err: Error = ruby + /// .eval::( + /// " /// class ExampleError < StandardError /// end /// module Tag @@ -217,20 +216,24 @@ impl Error { /// end /// raise SpecificError /// ", - /// ) - /// .unwrap_err(); + /// ) + /// .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")?)); /// - /// fn get(name: &str) -> T { - /// class::object().const_get::<_, T>(name).unwrap() + /// assert!(!err.is_kind_of(get::(ruby, "NoMethodError")?)); + /// assert!(!err.is_kind_of(get::(ruby, "Math")?)); + /// + /// Ok(()) /// } - /// assert!(err.is_kind_of(get::("SpecificError"))); - /// assert!(err.is_kind_of(get::("ExampleError"))); - /// assert!(err.is_kind_of(get::("StandardError"))); - /// assert!(err.is_kind_of(get::("Exception"))); - /// assert!(err.is_kind_of(get::("Tag"))); - /// - /// assert!(!err.is_kind_of(get::("NoMethodError"))); - /// assert!(!err.is_kind_of(get::("Math"))); + /// # Ruby::init(example).unwrap() /// ``` pub fn is_kind_of(&self, class: T) -> bool where @@ -273,7 +276,7 @@ impl Error { /// /// 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::mark`) to avoid being garbage collected. + /// [marked](`crate::gc::Marker::mark`) to avoid being garbage collected. pub fn value(&self) -> Option { match self.0 { ErrorType::Jump(_) => None, @@ -317,6 +320,19 @@ impl From for Error { } } +/// 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 @@ -334,6 +350,7 @@ impl From for Error { /// 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 {} @@ -369,9 +386,16 @@ impl From for OpaqueError { } } +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)] +#[derive(Debug, Clone, Copy)] #[repr(i32)] pub enum Tag { // None = 0, @@ -496,13 +520,9 @@ where let result = unsafe { let call_func_ptr = call_func:: as unsafe extern "C" fn(VALUE) -> VALUE; - #[cfg(ruby_lt_2_7)] - let call_func_ptr: unsafe extern "C" fn() -> VALUE = std::mem::transmute(call_func_ptr); 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; - #[cfg(ruby_lt_2_7)] - let call_ensure_ptr: unsafe extern "C" fn() -> VALUE = std::mem::transmute(call_ensure_ptr); let mut some_ensure = Some(ensure); let ensure_closure = &mut some_ensure as *mut Option as VALUE; rb_ensure( @@ -557,9 +577,10 @@ pub fn bug(s: &str) -> ! { /// Panics if called from a non-Ruby thread. See [`Ruby::warning`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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) diff --git a/src/exception.rs b/src/exception.rs index 327e7d56..1172aa9c 100644 --- a/src/exception.rs +++ b/src/exception.rs @@ -4,18 +4,16 @@ use std::fmt; -#[cfg(ruby_gte_2_7)] -use rb_sys::rb_eNoMatchingPatternError; #[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_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, + 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::{ @@ -87,15 +85,6 @@ impl Exception { pub fn exception_class(self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(self.class().as_rb_value()) } } - - #[doc(hidden)] - #[deprecated( - since = "0.6.0", - note = "Please use `ex.funcall(\"backtrace\", ())` instead." - )] - pub fn backtrace(self) -> Result, Error> { - self.funcall("backtrace", ()) - } } impl fmt::Display for Exception { @@ -110,12 +99,8 @@ impl fmt::Debug for Exception { unsafe { writeln!(f, "{}: {}", self.classname(), self)?; if let Ok(Some(backtrace)) = self.funcall::<_, _, Option>("backtrace", ()) { - for line in backtrace.each() { - if let Ok(line) = line { - writeln!(f, "{}", line)?; - } else { - break; - } + for line in backtrace { + writeln!(f, "{}", line)?; } } } @@ -635,8 +620,6 @@ impl Ruby { /// } /// # Ruby::init(example).unwrap() /// ``` - #[cfg(any(ruby_gte_2_7, docsrs))] - #[cfg_attr(docsrs, doc(cfg(ruby_gte_2_7)))] #[inline] pub fn exception_no_matching_pattern_error(&self) -> ExceptionClass { unsafe { ExceptionClass::from_rb_value_unchecked(rb_eNoMatchingPatternError) } @@ -1070,9 +1053,10 @@ impl Ruby { /// Panics if called from a non-Ruby thread. See [`Ruby::exception_arg_error`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1085,9 +1069,10 @@ pub fn arg_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See [`Ruby::exception_eof_error`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1100,9 +1085,10 @@ pub fn eof_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_enc_compat_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1115,9 +1101,10 @@ pub fn enc_compat_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_encoding_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1130,9 +1117,10 @@ pub fn encoding_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See [`Ruby::exception_exception`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1145,9 +1133,10 @@ pub fn exception() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See [`Ruby::exception_fatal`] for /// the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1160,9 +1149,10 @@ pub fn fatal() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_float_domain_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1175,9 +1165,10 @@ pub fn float_domain_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_frozen_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1190,9 +1181,10 @@ pub fn frozen_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See [`Ruby::exception_io_error`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1205,9 +1197,10 @@ pub fn io_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_index_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1220,9 +1213,10 @@ pub fn index_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See [`Ruby::exception_interrupt`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1235,9 +1229,10 @@ pub fn interrupt() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See [`Ruby::exception_key_error`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1250,9 +1245,10 @@ pub fn key_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See [`Ruby::exception_load_error`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1265,9 +1261,10 @@ pub fn load_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_local_jump_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1280,9 +1277,10 @@ pub fn local_jump_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_math_domain_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1295,9 +1293,10 @@ pub fn math_domain_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_name_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1309,12 +1308,11 @@ pub fn name_error() -> ExceptionClass { /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_no_matching_pattern_error`] for the non-panicking version. -#[cfg(any(ruby_gte_2_7, docsrs))] -#[cfg_attr(docsrs, doc(cfg(ruby_gte_2_7)))] #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1330,9 +1328,10 @@ pub fn no_matching_pattern_error() -> ExceptionClass { #[cfg(any(ruby_gte_3_1, docsrs))] #[cfg_attr(docsrs, doc(cfg(ruby_gte_3_1)))] #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1345,9 +1344,10 @@ pub fn no_matching_pattern_key_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_no_mem_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1360,9 +1360,10 @@ pub fn no_mem_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_no_method_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1375,9 +1376,10 @@ pub fn no_method_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_not_imp_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1390,9 +1392,10 @@ pub fn not_imp_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_range_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1405,9 +1408,10 @@ pub fn range_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_regexp_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1420,9 +1424,10 @@ pub fn regexp_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_runtime_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1435,9 +1440,10 @@ pub fn runtime_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_script_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1450,9 +1456,10 @@ pub fn script_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_security_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1465,9 +1472,10 @@ pub fn security_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_signal`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1480,9 +1488,10 @@ pub fn signal() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_standard_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1495,9 +1504,10 @@ pub fn standard_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_stop_iteration`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1510,9 +1520,10 @@ pub fn stop_iteration() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_syntax_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1525,9 +1536,10 @@ pub fn syntax_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_sys_stack_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1540,9 +1552,10 @@ pub fn sys_stack_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_system_call_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1555,9 +1568,10 @@ pub fn system_call_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_system_exit`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1570,9 +1584,10 @@ pub fn system_exit() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_thread_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1585,9 +1600,10 @@ pub fn thread_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See [`Ruby::exception_type_error`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1600,9 +1616,10 @@ pub fn type_error() -> ExceptionClass { /// Panics if called from a non-Ruby thread. See /// [`Ruby::exception_zero_div_error`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() diff --git a/src/fiber.rs b/src/fiber.rs new file mode 100644 index 00000000..a47359ed --- /dev/null +++ b/src/fiber.rs @@ -0,0 +1,588 @@ +//! 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) } +} diff --git a/src/float.rs b/src/float.rs index a1f7ea82..7ebe554f 100644 --- a/src/float.rs +++ b/src/float.rs @@ -109,6 +109,7 @@ impl Float { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{rb_assert, Float}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -119,9 +120,10 @@ impl Float { /// rb_assert!("f == 1.7272337110188890e-77", f); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -152,22 +154,26 @@ impl Float { /// # Examples /// /// ``` - /// use magnus::{rb_assert, Float}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{rb_assert, Error, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let pi = ruby.float_from_f64(3.141592); /// - /// let pi = 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(Float::from_f64(0.001)); - /// rb_assert!("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(Float::from_f64(0.01)); - /// rb_assert!("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(Float::from_f64(0.1)); - /// rb_assert!("r == 16/5r", r); + /// let r = pi.rationalize_with_prec(ruby.float_from_f64(1.)); + /// rb_assert!(ruby, "r == 3/1r", r); /// - /// let r = pi.rationalize_with_prec(Float::from_f64(1.)); - /// rb_assert!("r == 3/1r", r); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn rationalize_with_prec(self, prec: Self) -> RRational { unsafe { @@ -183,11 +189,15 @@ impl Float { /// # Examples /// /// ``` - /// use magnus::{rb_assert, Float}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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()); /// - /// let pi = Float::from_f64(3.141592); - /// rb_assert!("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())) } diff --git a/src/gc.rs b/src/gc.rs index b8090466..7a1506f3 100644 --- a/src/gc.rs +++ b/src/gc.rs @@ -5,12 +5,10 @@ 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_mark, - rb_gc_mark_locations, rb_gc_register_address, rb_gc_register_mark_object, rb_gc_start, - rb_gc_stat, rb_gc_unregister_address, VALUE, + 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, }; -#[cfg(ruby_gte_2_7)] -use rb_sys::{rb_gc_location, rb_gc_mark_movable}; use crate::{ error::{protect, Error}, @@ -20,7 +18,7 @@ use crate::{ Ruby, }; -mod private { +pub(crate) mod private { use super::*; pub trait Mark { @@ -110,8 +108,6 @@ impl Marker { /// 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`). - #[cfg(any(ruby_gte_2_7, docsrs))] - #[cfg_attr(docsrs, doc(cfg(ruby_gte_2_7)))] pub fn mark_movable(&self, value: T) where T: Mark, @@ -168,15 +164,13 @@ impl Compactor { /// /// 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`]. When - /// implementing + /// 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`. - #[cfg(any(ruby_gte_2_7, docsrs))] - #[cfg_attr(docsrs, doc(cfg(ruby_gte_2_7)))] pub fn location(&self, value: T) -> T where T: Locate, @@ -185,45 +179,6 @@ impl Compactor { } } -#[doc(hidden)] -#[deprecated(since = "0.6.0", note = "please use `Marker::mark` instead")] -pub fn mark(value: T) -where - T: ReprValue, -{ - Marker::new().mark(value) -} - -#[doc(hidden)] -#[deprecated(since = "0.6.0", note = "please use `Marker::mark_slice` instead")] -pub fn mark_slice(values: &[T]) -where - T: ReprValue, -{ - Marker::new().mark_slice(values) -} - -#[doc(hidden)] -#[deprecated(since = "0.6.0", note = "please use `Marker::mark_movable` instead")] -#[cfg(any(ruby_gte_2_7, docsrs))] -#[cfg_attr(docsrs, doc(cfg(ruby_gte_2_7)))] -pub fn mark_movable(value: T) -where - T: ReprValue, -{ - Marker::new().mark_movable(value) -} - -#[doc(hidden)] -#[deprecated(since = "0.6.0", note = "please use `Compactor::location` instead")] -#[cfg(any(ruby_gte_2_7, docsrs))] -pub fn location(value: T) -> T -where - T: ReprValue, -{ - Compactor::new().location(value) -} - /// Registers `value` to never be garbage collected. /// /// This is essentially a deliberate memory leak. @@ -231,16 +186,20 @@ where /// # Examples /// /// ``` -/// use magnus::{gc, RArray, RString}; -/// # let _cleanup = unsafe { magnus::embed::init() }; +/// 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); /// -/// // will never be collected -/// let root = RArray::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(); /// -/// // won't be collected while it is in out `root` array -/// let s = RString::new("example"); -/// root.push(s).unwrap(); +/// Ok(()) +/// } +/// # Ruby::init(example).unwrap() /// ``` pub fn register_mark_object(value: T) where @@ -260,20 +219,24 @@ where /// # Examples /// /// ``` -/// use magnus::{gc, RString}; -/// # let _cleanup = unsafe { magnus::embed::init() }; +/// use magnus::{gc, Error, Ruby}; /// -/// let s = RString::new("example"); +/// 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); +/// // 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); /// -/// // allow s to be collected -/// gc::unregister_address(&*boxed); -/// drop(boxed); +/// Ok(()) +/// } +/// # Ruby::init(example).unwrap() /// ``` pub fn register_address(valref: &T) where @@ -289,20 +252,24 @@ where /// # Examples /// /// ``` -/// use magnus::{gc, RString}; -/// # let _cleanup = unsafe { magnus::embed::init() }; +/// use magnus::{gc, Error, Ruby}; /// -/// let s = RString::new("example"); +/// 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); +/// // 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); /// -/// // allow s to be collected -/// gc::unregister_address(&*boxed); -/// drop(boxed); +/// Ok(()) +/// } +/// # Ruby::init(example).unwrap() /// ``` pub fn unregister_address(valref: &T) where @@ -521,6 +488,7 @@ impl Ruby { /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// use magnus::gc; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -534,9 +502,10 @@ impl Ruby { /// } /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -557,6 +526,7 @@ pub fn disable() -> bool { /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// use magnus::gc; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -570,9 +540,10 @@ pub fn disable() -> bool { /// } /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -596,15 +567,17 @@ pub fn enable() -> bool { /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// use magnus::gc; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// gc::start(); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -627,6 +600,7 @@ pub fn start() { /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// use magnus::gc; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -640,9 +614,10 @@ pub fn start() { /// gc::adjust_memory_usage(-(mem_size as isize)); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -659,6 +634,7 @@ pub fn adjust_memory_usage(diff: isize) { /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// use magnus::gc; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -667,9 +643,10 @@ pub fn adjust_memory_usage(diff: isize) { /// assert!(gc::count() > before); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -685,15 +662,17 @@ pub fn count() -> usize { /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// use magnus::gc; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(gc::stat("heap_live_slots").unwrap() > 1); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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 @@ -713,6 +692,7 @@ where /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// use magnus::{gc, Symbol}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -721,9 +701,10 @@ where /// assert!(live_slots > 1); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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() diff --git a/src/integer.rs b/src/integer.rs index 72e6303c..2418f575 100644 --- a/src/integer.rs +++ b/src/integer.rs @@ -1,6 +1,14 @@ -use std::fmt; +use std::{ + fmt, + ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}, + os::raw::c_long, +}; -use rb_sys::{rb_ll2inum, rb_to_int, rb_ull2inum, ruby_special_consts, ruby_value_type, VALUE}; +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}, @@ -150,6 +158,7 @@ impl Integer { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{rb_assert, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -164,9 +173,10 @@ impl Integer { /// ); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -182,6 +192,7 @@ impl Integer { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{rb_assert, Integer}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -192,9 +203,10 @@ impl Integer { /// ); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -477,6 +489,36 @@ impl Integer { 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 { @@ -515,3 +557,154 @@ impl TryConvert for Integer { } } } + +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; + } +} diff --git a/src/into_value.rs b/src/into_value.rs index 08c0edae..b1a6a2e1 100644 --- a/src/into_value.rs +++ b/src/into_value.rs @@ -33,9 +33,10 @@ pub trait IntoValue: Sized { /// Panics if called from a non-Ruby thread. See /// [`IntoValue::into_value_with`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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!()) diff --git a/src/lib.rs b/src/lib.rs index 1849f5a6..a1e3dcdd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,7 +57,7 @@ //! # Examples //! //! ``` -//! use magnus::{class, define_module, function, method, prelude::*, Error}; +//! use magnus::{function, method, prelude::*, Error, Ruby}; //! //! #[magnus::wrap(class = "Euclid::Point", free_immediately, size)] //! struct Point { @@ -84,22 +84,22 @@ //! } //! //! #[magnus::init] -//! fn init() -> Result<(), Error> { -//! let module = define_module("Euclid")?; -//! let class = module.define_class("Point", class::object())?; +//! 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(()) //! } -//! # let _cleanup = unsafe { magnus::embed::init() }; -//! # init().unwrap(); +//! # 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 //! @@ -222,7 +222,7 @@ // * `rb_add_event_hook`: // * `rb_add_event_hook2`: //! * `rb_alias`: [`Module::define_alias`]. -// * `rb_alias_variable`: +//! * `rb_alias_variable`: [`Ruby::alias_variable`]. // * `RB_ALLOC`: // * `RB_ALLOCV`: // * `RB_ALLOCV_END`: @@ -324,7 +324,7 @@ // * `rb_big_modulo`: // * `rb_big_mul`: // * `rb_big_new`: -// * `rb_big_norm`: +//! * `rb_big_norm`: [`Integer::norm`]. // * `rb_big_or`: // * `rb_big_pack`: // * `rb_big_plus`: @@ -334,7 +334,6 @@ // * `rb_big_sign`: // * `rb_big_unpack`: // * `rb_big_xor`: -//! * `rb_binding_new`: [`Binding::new`]. Unimplemented >= Ruby 3.2. //! * `rb_block_call`: See [`Value::block_call`]. //! * `rb_block_call_kw`: [`Value::block_call`]. //! * `rb_block_given_p`: [`block::block_given`]. @@ -458,6 +457,7 @@ //! //! ## `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`]. @@ -694,12 +694,13 @@ // * `rb_fd_term`: // * `rb_fd_zero`: // * `rb_feature_provided`: -// * `rb_fiber_alive_p`: -// * `rb_fiber_current`: -// * `rb_fiber_new`: -// * `rb_fiber_raise`: -// * `rb_fiber_resume`: -// * `rb_fiber_resume_kw`: +//! * `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`: @@ -724,10 +725,10 @@ // * `rb_fiber_scheduler_process_wait`: // * `rb_fiber_scheduler_set`: // * `rb_fiber_scheduler_unblock`: -// * `rb_fiber_transfer`: -// * `rb_fiber_transfer_kw`: -// * `rb_fiber_yield`: -// * `rb_fiber_yield_kw`: +//! * `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). @@ -820,11 +821,11 @@ //! * `rb_gc_enable`: [`gc::enable`]. // * `RB_GC_GUARD`: // * `rb_gc_latest_gc_info`: -//! * `rb_gc_location`: [`gc::location`]. -//! * `rb_gc_mark`: [`gc::mark`]. -//! * `rb_gc_mark_locations`: [`gc::mark_slice`]. +//! * `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::mark_movable`]. +//! * `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`]. @@ -871,7 +872,7 @@ // * `rb_hash_ifnone`: // * `rb_hash_iter_lev`: //! * `rb_hash_lookup`: [`RHash::lookup`]. -//! * `rb_hash_lookup2`: [`RHash::get`]. +//! * `rb_hash_lookup2`: [`RHash::lookup2`]. //! * `rb_hash_new`: [`RHash::new`]. //! * `rb_hash_new_capa`: [`RHash::with_capacity`]. // * `rb_hash_set_ifnone`: @@ -1084,13 +1085,13 @@ // * `rb_mod_sys_fail`: // * `rb_mod_sys_fail_str`: // * `rb_must_asciicompat`: -// * `rb_mutex_lock`: -// * `rb_mutex_locked_p`: -// * `rb_mutex_new`: -// * `rb_mutex_sleep`: -// * `rb_mutex_synchronize`: -// * `rb_mutex_trylock`: -// * `rb_mutex_unlock`: +//! * `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`: @@ -1178,7 +1179,7 @@ // * `rb_obj_instance_eval`: // * `rb_obj_instance_exec`: // * `rb_obj_instance_variables`: -// * `rb_obj_is_fiber`: +//! * `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`: @@ -1358,7 +1359,7 @@ // * `rb_struct_alloc_noinit`: //! * `rb_struct_aref`: [`RStruct::aref`]. //! * `rb_struct_aset`: [`RStruct::aset`]. -//! * `rb_struct_define`: [`r_struct::define_struct`]. +//! * `rb_struct_define`: [`Ruby::define_struct`]. // * `rb_struct_define_under`: // * `rb_struct_define_without_accessor`: // * `rb_struct_define_without_accessor_under`: @@ -1506,47 +1507,47 @@ //! * `RB_TEST`: [`Value::to_bool`] / [`TryConvert`] / [`Value::try_convert`]. // * `rb_thread_add_event_hook`: // * `rb_thread_add_event_hook2`: -// * `rb_thread_alone`: +//! * `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`: -// * `rb_thread_create`: -// * `rb_thread_current`: -// * `rb_thread_fd_close`: +//! * `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`: -// * `rb_thread_interrupted`: -// * `rb_thread_kill`: -// * `rb_thread_local_aref`: -// * `rb_thread_local_aset`: -// * `rb_thread_main`: +//! * `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`: -// * `rb_thread_schedule`: -// * `rb_thread_sleep`: -// * `rb_thread_sleep_deadly`: -// * `rb_thread_sleep_forever`: -// * `rb_thread_stop`: -// * `rb_thread_wait_fd`: -// * `rb_thread_wait_for`: -// * `rb_thread_wakeup`: -// * `rb_thread_wakeup_alive`: +//! * `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`: +//! * `rb_time_new`: [`Ruby::time_new`]. // * `rb_time_num_new`: // * `rb_time_timespec`: // * `rb_time_timespec_interval`: // * `rb_time_timespec_new`: -// * `rb_time_timeval`: -// * `rb_time_utc_offset`: +//! * `rb_time_timeval`: [`TryConvert`]. +//! * `rb_time_utc_offset`: [`Time::utc_offset`]. // * `rb_tolower`: // * `rb_toupper`: //! * `rb_to_encoding`: [`TryConvert`] or [`Value::try_convert`]. @@ -1619,7 +1620,7 @@ // * `rb_vsprintf`: // * `rb_w32_fd_copy`: // * `rb_w32_fd_dup`: -// * `rb_waitpid`: +//! * `rb_waitpid`: [`Ruby::waitpid`]. // * `rb_warn`: //! * `rb_warning`: [`error::warning`]. // * `rb_write_error`: @@ -1791,7 +1792,6 @@ mod macros; mod api; -mod binding; pub mod block; pub mod class; #[cfg(feature = "embed")] @@ -1801,14 +1801,19 @@ 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::{ @@ -1817,7 +1822,7 @@ pub mod prelude { value::ReprValue as _, }; } -mod r_array; +pub mod r_array; mod r_bignum; mod r_complex; mod r_file; @@ -1836,23 +1841,24 @@ mod range; 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}; -#[cfg(ruby_lt_2_7)] -use ::rb_sys::rb_require; -#[cfg(ruby_gte_2_7)] -use ::rb_sys::rb_require_string; use ::rb_sys::{ - 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_set_errinfo, VALUE, + 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::{ @@ -1865,6 +1871,7 @@ pub use crate::{ integer::Integer, into_value::{ArgList, IntoValue, IntoValueFromNative, KwArgs, RArrayArgList}, module::{Attr, Module, RModule}, + mutex::Mutex, numeric::Numeric, object::Object, r_array::RArray, @@ -1882,38 +1889,103 @@ pub use crate::{ r_typed_data::RTypedData, range::Range, symbol::Symbol, + thread::Thread, + time::Time, try_convert::TryConvert, typed_data::{DataType, DataTypeFunctions, TypedData}, value::{Fixnum, StaticSymbol, Value}, }; -#[allow(deprecated)] -pub use crate::{ - binding::Binding, - value::{QFALSE, QNIL, QTRUE}, -}; use crate::{ error::protect, method::Method, r_string::IntoRString, - value::{private::ReprValue as _, ReprValue}, + 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)] -#[deprecated( - since = "0.6.0", - note = "please use `value::Lazy` or `DataType::builder` `const` constructors to set a `static` instead" -)] #[macro_export] -macro_rules! memoize { - ($type:ty: $val:expr) => {{ - static INIT: std::sync::Once = std::sync::Once::new(); - static mut VALUE: Option<$type> = None; - unsafe { - INIT.call_once(|| { - VALUE = Some($val); - }); - VALUE.as_ref().unwrap() - } +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)*); }}; } @@ -2109,6 +2181,40 @@ impl Ruby { 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 @@ -2298,7 +2404,6 @@ impl Ruby { /// } /// # Ruby::init(example).unwrap() /// ``` - #[cfg(ruby_gte_2_7)] pub fn require(&self, feature: T) -> Result where T: IntoRString, @@ -2308,33 +2413,12 @@ impl Ruby { .and_then(TryConvert::try_convert) } - /// Finds and loads the given feature if not already loaded. - /// - /// # Examples - /// - /// ``` - /// use magnus::{rb_assert, Error, Ruby}; - /// - /// fn example(ruby: &Ruby) -> Result<(), Error> { - /// assert!(ruby.require("net/http")?); - /// - /// Ok(()) - /// } - /// # Ruby::init(example).unwrap() - /// ``` - #[cfg(ruby_lt_2_7)] - pub fn require(&self, feature: &str) -> Result { - let feature = CString::new(feature).unwrap(); - protect(|| unsafe { Value::new(rb_require(feature.as_ptr())) }) - .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 or [`Binding::eval`] for an - /// alternative that supports utf-8. + /// [`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. @@ -2387,6 +2471,7 @@ impl Ruby { /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// use magnus::{class, define_class, rb_assert}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -2394,9 +2479,10 @@ impl Ruby { /// rb_assert!("Example.is_a?(Class)"); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -2412,6 +2498,7 @@ pub fn define_class(name: &str, superclass: RClass) -> Result { /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// use magnus::{define_module, rb_assert}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -2420,9 +2507,10 @@ pub fn define_class(name: &str, superclass: RClass) -> Result { /// rb_assert!("!Example.is_a?(Class)"); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -2438,6 +2526,7 @@ pub fn define_module(name: &str) -> Result { /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// use magnus::{define_error, exception, rb_assert}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -2446,9 +2535,10 @@ pub fn define_module(name: &str) -> Result { /// rb_assert!("ExampleError < Exception"); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -2464,6 +2554,7 @@ pub fn define_error(name: &str, superclass: ExceptionClass) -> Result Result(name: &str, initial: T) -> Result<*mut Value, Error> where @@ -2498,6 +2590,7 @@ where /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// use magnus::{define_global_const, rb_assert}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -2505,9 +2598,10 @@ where /// rb_assert!("EXAMPLE == 42"); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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 @@ -2526,6 +2620,7 @@ where /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// use magnus::{define_global_function, function, rb_assert}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -2537,9 +2632,10 @@ where /// rb_assert!(r#"greet("world") == "Hello, world!""#); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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 @@ -2558,6 +2654,7 @@ where /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// use magnus::{backref_get, RRegexp}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -2576,9 +2673,10 @@ where /// ); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -2596,6 +2694,7 @@ pub fn backref_get() -> Option { /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// use magnus::{ /// current_receiver, define_global_function, method, prelude::*, rb_assert, Error, Value, /// }; @@ -2609,9 +2708,10 @@ pub fn backref_get() -> Option { /// rb_assert!("example"); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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 @@ -2634,6 +2734,7 @@ where /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// use magnus::{call_super, define_class, eval, function, prelude::*, rb_assert, Error}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -2659,9 +2760,10 @@ where /// rb_assert!(r#"B.new.example == "Hello from A, and hello from B""#) /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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 @@ -2681,16 +2783,17 @@ where /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// # let _cleanup = unsafe { magnus::embed::init() }; /// use magnus::require; /// /// assert!(require("net/http").unwrap()); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + not(feature = "old-api"), deprecated(note = "please use `Ruby::require` instead") )] -#[cfg(ruby_gte_2_7)] +#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn require(feature: T) -> Result where @@ -2699,31 +2802,6 @@ where get_ruby!().require(feature) } -/// 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 -/// -/// ``` -/// # let _cleanup = unsafe { magnus::embed::init() }; -/// use magnus::require; -/// -/// assert!(require("net/http").unwrap()); -/// ``` -#[cfg_attr( - not(feature = "friendly-api"), - deprecated(note = "please use `Ruby::require` instead") -)] -#[cfg(ruby_lt_2_7)] -#[inline] -pub fn require(feature: &str) -> Result { - 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 diff --git a/src/macros.rs b/src/macros.rs index 7ee09879..2c0c6c31 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -15,7 +15,6 @@ macro_rules! debug_assert_value { ::rb_sys::ruby_value_type::RUBY_T_NONE | ::rb_sys::ruby_value_type::RUBY_T_ZOMBIE => { panic!("Attempting to access garbage collected Object") } - #[cfg(ruby_gte_2_7)] ::rb_sys::ruby_value_type::RUBY_T_MOVED => { panic!("Attempting to access garbage collected Object") } diff --git a/src/method.rs b/src/method.rs index a1b51222..2211c89c 100644 --- a/src/method.rs +++ b/src/method.rs @@ -13,7 +13,7 @@ use crate::{ do_yield_iter, do_yield_splat_iter, do_yield_values_iter, Proc, Yield, YieldSplat, YieldValues, }, - error::{raise, Error}, + error::{raise, Error, IntoError}, into_value::{ArgList, IntoValue}, r_array::RArray, try_convert::TryConvert, @@ -74,12 +74,15 @@ mod private { fn into_return_value(self) -> Result; } - impl ReturnValue for Result + impl ReturnValue for Result where T: IntoValue, + E: IntoError, { fn into_return_value(self) -> Result { - self.map(|val| unsafe { val.into_value_unchecked() }) + let ruby = unsafe { Ruby::get_unchecked() }; + self.map(|val| val.into_value_with(&ruby)) + .map_err(|err| err.into_error(&ruby)) } } @@ -88,83 +91,92 @@ mod private { T: IntoValue, { fn into_return_value(self) -> Result { - Ok(self).into_return_value() + Ok::(self).into_return_value() } } - impl ReturnValue for Yield + impl ReturnValue for Result, E> where I: Iterator, T: IntoValue, + E: IntoError, { fn into_return_value(self) -> Result { - match self { + let ruby = unsafe { Ruby::get_unchecked() }; + self.map(|i| match i { Yield::Iter(iter) => unsafe { do_yield_iter(iter); - Ok(Ruby::get_unchecked().qnil().as_value()) + ruby.qnil().as_value() }, - Yield::Enumerator(e) => Ok(unsafe { e.into_value_unchecked() }), - } + Yield::Enumerator(e) => e.into_value_with(&ruby), + }) + .map_err(|err| err.into_error(&ruby)) } } - impl ReturnValue for Result, Error> + impl ReturnValue for Yield where I: Iterator, T: IntoValue, { fn into_return_value(self) -> Result { - self?.into_return_value() + Ok::(self).into_return_value() } } - impl ReturnValue for YieldValues + impl ReturnValue for Result, E> where I: Iterator, T: ArgList, + E: IntoError, { fn into_return_value(self) -> Result { - match self { + let ruby = unsafe { Ruby::get_unchecked() }; + self.map(|i| match i { YieldValues::Iter(iter) => unsafe { do_yield_values_iter(iter); - Ok(Ruby::get_unchecked().qnil().as_value()) + ruby.qnil().as_value() }, - YieldValues::Enumerator(e) => Ok(unsafe { e.into_value_unchecked() }), - } + YieldValues::Enumerator(e) => e.into_value_with(&ruby), + }) + .map_err(|err| err.into_error(&ruby)) } } - impl ReturnValue for Result, Error> + impl ReturnValue for YieldValues where I: Iterator, T: ArgList, { fn into_return_value(self) -> Result { - self?.into_return_value() + Ok::(self).into_return_value() } } - impl ReturnValue for YieldSplat + impl ReturnValue for Result, E> where I: Iterator, + E: IntoError, { fn into_return_value(self) -> Result { - match self { + let ruby = unsafe { Ruby::get_unchecked() }; + self.map(|i| match i { YieldSplat::Iter(iter) => unsafe { do_yield_splat_iter(iter); - Ok(Ruby::get_unchecked().qnil().as_value()) + ruby.qnil().as_value() }, - YieldSplat::Enumerator(e) => Ok(unsafe { e.into_value_unchecked() }), - } + YieldSplat::Enumerator(e) => e.into_value_with(&ruby), + }) + .map_err(|err| err.into_error(&ruby)) } } - impl ReturnValue for Result, Error> + impl ReturnValue for YieldSplat where I: Iterator, { fn into_return_value(self) -> Result { - self?.into_return_value() + Ok::(self).into_return_value() } } @@ -178,9 +190,12 @@ mod private { } } - impl InitReturn for Result<(), Error> { + impl InitReturn for Result<(), E> + where + E: IntoError, + { fn into_init_return(self) -> Result<(), Error> { - self + self.map_err(|err| err.into_error(&unsafe { Ruby::get_unchecked() })) } } @@ -367,18 +382,19 @@ where #[doc(hidden)] pub trait Block where - Self: Sized + FnMut(&[Value], Option) -> Res, + Self: Sized + FnOnce(&Ruby, &[Value], Option) -> Res, Res: BlockReturn, { #[inline] unsafe fn call_convert_value( - mut self, + 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)(args, Proc::from_value(blockarg)).into_block_return() + (self)(&ruby, args, Proc::from_value(blockarg)).into_block_return() } #[inline] @@ -398,7 +414,75 @@ where impl Block for Func where - Func: FnMut(&[Value], Option) -> Res, + 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, { } @@ -732,20 +816,20 @@ seq!(N in 0..=16 { /// # Examples /// /// ``` -/// use magnus::{class, define_class, method, prelude::*, Error}; +/// 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() -> Result<(), Error> { -/// let class = define_class("String", class::object())?; +/// 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().unwrap(); +/// # let cleanup = unsafe { magnus::embed::init() }; +/// # init(&cleanup).unwrap(); /// ``` #[macro_export] macro_rules! method { @@ -1520,11 +1604,11 @@ seq!(N in 0..=16 { /// } /// /// #[magnus::init] -/// fn init() { -/// magnus::define_global_function("distance", magnus::function!(distance, 2)); +/// fn init(ruby: &magnus::Ruby) { +/// ruby.define_global_function("distance", magnus::function!(distance, 2)); /// } -/// # let _cleanup = unsafe { magnus::embed::init() }; -/// # init(); +/// # let cleanup = unsafe { magnus::embed::init() }; +/// # init(&cleanup); /// ``` #[macro_export] macro_rules! function { diff --git a/src/module.rs b/src/module.rs index 21724367..a3f22217 100644 --- a/src/module.rs +++ b/src/module.rs @@ -104,6 +104,7 @@ impl RModule { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{class, prelude::*, RModule}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -111,9 +112,10 @@ impl RModule { /// assert!(module.is_kind_of(class::module())); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -126,26 +128,29 @@ impl RModule { /// # Examples /// /// ``` - /// use magnus::{define_module, function, r_string, rb_assert, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{function, r_string, rb_assert, Error, RString, Ruby}; /// - /// fn greet() -> RString { - /// r_string!("Hello, world!") + /// fn greet(ruby: &Ruby) -> RString { + /// r_string!(ruby, "Hello, world!") /// } /// - /// let module = define_module("Greeting").unwrap(); - /// module - /// .define_module_function("greet", function!(greet, 0)) - /// .unwrap(); + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let module = ruby.define_module("Greeting")?; + /// module.define_module_function("greet", function!(greet, 0))?; /// - /// rb_assert!(r#"Greeting.greet == "Hello, world!""#); + /// rb_assert!(ruby, r#"Greeting.greet == "Hello, world!""#); /// - /// rb_assert!( - /// r#" - /// include 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 @@ -214,12 +219,16 @@ pub trait Module: Object + ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{class, define_module, prelude::*, rb_assert}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, rb_assert, Error, Ruby}; /// - /// let outer = define_module("Outer").unwrap(); - /// outer.define_class("Inner", class::object()).unwrap(); - /// rb_assert!("Outer::Inner.is_a?(Class)"); + /// 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 @@ -243,13 +252,17 @@ pub trait Module: Object + ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{define_module, prelude::*, rb_assert}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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)"); /// - /// let outer = define_module("Outer").unwrap(); - /// outer.define_module("Inner").unwrap(); - /// rb_assert!("Outer::Inner.is_a?(Module)"); - /// rb_assert!("!Outer::Inner.is_a?(Class)"); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn define_module(self, name: T) -> Result where @@ -269,15 +282,17 @@ pub trait Module: Object + ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{define_module, exception, prelude::*, rb_assert}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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"); /// - /// let outer = define_module("Outer").unwrap(); - /// outer - /// .define_error("InnerError", exception::standard_error()) - /// .unwrap(); - /// rb_assert!("Outer::InnerError.is_a?(Class)"); - /// rb_assert!("Outer::InnerError < Exception"); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn define_error(self, name: T, superclass: ExceptionClass) -> Result where @@ -295,23 +310,25 @@ pub trait Module: Object + ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{class, function, prelude::*, rb_assert, RClass, RModule}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{function, prelude::*, rb_assert, Error, RClass, Ruby}; /// - /// fn example() -> i64 { + /// fn test() -> i64 { /// 42 /// } /// - /// let module = RModule::new(); - /// module - /// .define_method("example", function!(example, 0)) - /// .unwrap(); + /// 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 class = RClass::new(class::object()).unwrap(); - /// class.include_module(module).unwrap(); + /// let obj = class.new_instance(())?; + /// rb_assert!(ruby, "obj.test == 42", obj); /// - /// let obj = class.new_instance(()).unwrap(); - /// rb_assert!("obj.example == 42", obj); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn include_module(self, module: RModule) -> Result<(), Error> { protect(|| { @@ -329,33 +346,34 @@ pub trait Module: Object + ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{call_super, eval, function, prelude::*, rb_assert, Error, RClass, RModule}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{function, prelude::*, rb_assert, Error, RClass, Ruby}; /// - /// fn example() -> Result { - /// Ok(call_super::<_, i64>(())? + 2) + /// fn test(ruby: &Ruby) -> Result { + /// Ok(ruby.call_super::<_, i64>(())? + 2) /// } /// - /// let module = RModule::new(); - /// module - /// .define_method("example", function!(example, 0)) - /// .unwrap(); - /// - /// let class: RClass = eval( - /// r#" - /// class Example - /// def example - /// 40 - /// end - /// end - /// Example - /// "#, - /// ) - /// .unwrap(); - /// class.prepend_module(module).unwrap(); - /// - /// let obj = class.new_instance(()).unwrap(); - /// rb_assert!("obj.example == 42", obj); + /// 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(|| { @@ -370,12 +388,16 @@ pub trait Module: Object + ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{class, rb_assert, Module}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{rb_assert, Error, Module, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// ruby.class_array().const_set("EXAMPLE", 42)?; /// - /// class::array().const_set("EXAMPLE", 42).unwrap(); + /// rb_assert!(ruby, "Array::EXAMPLE == 42"); /// - /// rb_assert!("Array::EXAMPLE == 42"); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn const_set(self, name: T, value: U) -> Result<(), Error> where @@ -397,20 +419,23 @@ pub trait Module: Object + ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{class, eval, rb_assert, Module, RClass, Value}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// eval::( - /// " - /// class Example - /// VALUE = 42 - /// end - /// ", - /// ) - /// .unwrap(); - /// - /// let class = class::object().const_get::<_, RClass>("Example").unwrap(); - /// rb_assert!("klass::VALUE == 42", klass = class); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn const_get(self, name: T) -> Result where @@ -431,13 +456,17 @@ pub trait Module: Object + ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{class, prelude::*, Module, RClass}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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)); /// - /// let a = RClass::new(class::object()).unwrap(); - /// let b = RClass::new(a).unwrap(); - /// assert!(b.is_inherited(a)); - /// assert!(!a.is_inherited(b)); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn is_inherited(self, other: T) -> bool where @@ -457,15 +486,20 @@ pub trait Module: Object + ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{class, rb_assert, Module}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{rb_assert, Error, Module, Ruby}; /// - /// let ary = class::string().ancestors(); + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let ary = ruby.class_string().ancestors(); /// - /// rb_assert!( - /// "ary == [String, Comparable, Object, Kernel, BasicObject]", - /// ary, - /// ); + /// 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())) } @@ -476,20 +510,24 @@ pub trait Module: Object + ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{class, rb_assert, method, Module}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{method, rb_assert, Error, Module, Ruby}; /// /// fn escape_unicode(s: String) -> String { /// s.escape_unicode().to_string() /// } /// - /// class::string() - /// .define_method("escape_unicode", method!(escape_unicode, 0)) - /// .unwrap(); + /// 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}""#, + /// ); /// - /// rb_assert!( - /// 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 @@ -518,8 +556,7 @@ pub trait Module: Object + ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{class, eval, exception, function, rb_assert, Module, Value}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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 == '~' { @@ -529,26 +566,29 @@ pub trait Module: Object + ReprValue + Copy { /// } /// } /// - /// class::string() - /// .define_private_method("percent_encode_char", function!(percent_encode, 1)) - /// .unwrap(); - /// - /// eval::( - /// r#" - /// class String - /// def percent_encode - /// chars.map {|c| percent_encode_char(c)}.join("") - /// end - /// end - /// "#, - /// ) - /// .unwrap(); - /// - /// rb_assert!(r#""foo bar".percent_encode == "foo%20bar""#); - /// - /// assert!(eval::(r#"" ".percent_encode_char(" ")"#) - /// .unwrap_err() - /// .is_kind_of(exception::no_method_error())); + /// 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 @@ -575,8 +615,7 @@ pub trait Module: Object + ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{class, eval, exception, method, rb_assert, Module, Value}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{method, rb_assert, Error, Module, Ruby, Value}; /// /// fn escape_unicode(s: String) -> String { /// s.escape_unicode().to_string() @@ -586,33 +625,35 @@ pub trait Module: Object + ReprValue + Copy { /// c.is_control() || c.is_whitespace() /// } /// - /// class::string() - /// .define_method("escape_unicode", method!(escape_unicode, 0)) - /// .unwrap(); - /// class::string() - /// .define_protected_method("invisible?", method!(is_invisible, 0)) - /// .unwrap(); - /// - /// eval::( - /// r#" - /// class String - /// def escape_invisible - /// chars.map {|c| c.invisible? ? c.escape_unicode : c}.join("") - /// end - /// end - /// "#, - /// ) - /// .unwrap(); - /// - /// rb_assert!( - /// r#" - /// "🤖\tfoo bar".escape_invisible == "🤖\\u{9}foo\\u{20}bar" - /// "#, - /// ); - /// - /// assert!(eval::(r#"" ".invisible?"#) - /// .unwrap_err() - /// .is_kind_of(exception::no_method_error())); + /// 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 @@ -641,15 +682,19 @@ pub trait Module: Object + ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{class, eval, prelude::*, rb_assert, Attr, Module, RClass, Value}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{eval, prelude::*, rb_assert, Attr, Error, Module, RClass, Ruby, Value}; /// - /// let class = RClass::new(class::object()).unwrap(); - /// class.define_attr("example", Attr::ReadWrite).unwrap(); + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let class = RClass::new(ruby.class_object())?; + /// class.define_attr("example", Attr::ReadWrite)?; /// - /// let obj = class.new_instance(()).unwrap(); - /// let _: Value = eval!("obj.example = 42", obj).unwrap(); - /// rb_assert!("obj.example == 42", obj); + /// 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 @@ -677,21 +722,23 @@ pub trait Module: Object + ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{class, function, prelude::*, rb_assert, Module, RClass}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{function, prelude::*, rb_assert, Error, Module, RClass, Ruby}; /// - /// fn example() -> i64 { + /// fn test() -> i64 { /// 42 /// } /// - /// let class = RClass::new(class::object()).unwrap(); - /// class - /// .define_method("example", function!(example, 0)) - /// .unwrap(); - /// class.define_alias("test", "example").unwrap(); + /// 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); /// - /// let obj = class.new_instance(()).unwrap(); - /// rb_assert!("obj.test == 42", obj); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn define_alias(self, dst: T, src: U) -> Result<(), Error> where @@ -948,9 +995,10 @@ impl Ruby { /// Panics if called from a non-Ruby thread. See [`Ruby::module_comparable`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -963,9 +1011,10 @@ pub fn comparable() -> RModule { /// Panics if called from a non-Ruby thread. See [`Ruby::module_enumerable`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -978,9 +1027,10 @@ pub fn enumerable() -> RModule { /// Panics if called from a non-Ruby thread. See [`Ruby::module_errno`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -993,9 +1043,10 @@ pub fn errno() -> RModule { /// Panics if called from a non-Ruby thread. See [`Ruby::module_file_test`] for /// the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1008,9 +1059,10 @@ pub fn file_test() -> RModule { /// Panics if called from a non-Ruby thread. See [`Ruby::module_gc`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1023,9 +1075,10 @@ pub fn gc() -> RModule { /// Panics if called from a non-Ruby thread. See [`Ruby::module_kernel`] for /// the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1038,9 +1091,10 @@ pub fn kernel() -> RModule { /// Panics if called from a non-Ruby thread. See [`Ruby::module_math`] for the /// non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1053,9 +1107,10 @@ pub fn math() -> RModule { /// Panics if called from a non-Ruby thread. See [`Ruby::module_process`] for /// the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1068,9 +1123,10 @@ pub fn process() -> RModule { /// Panics if called from a non-Ruby thread. See [`Ruby::module_wait_readable`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -1083,9 +1139,10 @@ pub fn wait_readable() -> RModule { /// Panics if called from a non-Ruby thread. See [`Ruby::module_wait_writable`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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() diff --git a/src/mutex.rs b/src/mutex.rs new file mode 100644 index 00000000..6c71ac48 --- /dev/null +++ b/src/mutex.rs @@ -0,0 +1,304 @@ +//! 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() + },), + ) + }) + } +} diff --git a/src/numeric.rs b/src/numeric.rs index ec0580bb..1dbd2444 100644 --- a/src/numeric.rs +++ b/src/numeric.rs @@ -33,28 +33,36 @@ pub trait Numeric: ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{Integer, Numeric}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Numeric, Ruby}; /// - /// let a = Integer::from_i64(2); - /// let b = Integer::from_i64(3); - /// let c: i64 = a.coerce_bin(b, "+").unwrap(); - /// assert_eq!(c, 5); + /// 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::{Float, Integer, Numeric, Value}; - /// # let _cleanup = unsafe { magnus::embed::init() }; - /// - /// let a = Integer::from_i64(2); - /// let b = Float::from_f64(3.5); - /// let c: Value = a.coerce_bin(b, "+").unwrap(); - /// let c = Float::from_value(c); - /// assert!(c.is_some()); - /// assert_eq!(c.unwrap().to_f64(), 5.5); + /// 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 @@ -94,13 +102,17 @@ pub trait Numeric: ReprValue + Copy { /// ``` /// use std::num::NonZeroI64; /// - /// use magnus::{Float, Numeric, RRational}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// let a = RRational::new(1, NonZeroI64::new(4).unwrap()); - /// let b = Float::from_f64(0.3); - /// let result: i64 = a.coerce_cmp(b, "<=>").unwrap(); - /// assert_eq!(result, -1); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn coerce_cmp(self, other: T, op: ID) -> Result where @@ -137,13 +149,17 @@ pub trait Numeric: ReprValue + Copy { /// ``` /// use std::num::NonZeroI64; /// - /// use magnus::{Float, Numeric, RRational}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Numeric, Ruby}; /// - /// let a = Float::from_f64(0.3); - /// let b = RRational::new(1, NonZeroI64::new(4).unwrap()); - /// let result: bool = a.coerce_cmp(b, "<=").unwrap(); - /// assert_eq!(result, false); + /// 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 @@ -178,13 +194,17 @@ pub trait Numeric: ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{Integer, Numeric}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// let a = Integer::from_i64(0b00000011); - /// let b = Integer::from_i64(0b00001110); - /// let result: i64 = a.coerce_cmp(b, "^").unwrap(); - /// assert_eq!(result, 0b00001101); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn coerce_bit(self, other: T, op: ID) -> Result where @@ -213,18 +233,22 @@ pub trait Numeric: ReprValue + Copy { /// ``` /// use std::num::NonZeroI64; /// -/// use magnus::{numeric::NumericValue, prelude::*, Float, Integer, RRational}; -/// # let _cleanup = unsafe { magnus::embed::init() }; +/// 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 a = Integer::from_i64(1); -/// let b = RRational::new(1, NonZeroI64::new(2).unwrap()); -/// let c = Float::from_f64(0.3); -/// let d = 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); /// -/// let result: NumericValue = a.coerce_bin(b, "+").unwrap(); -/// let result: NumericValue = result.coerce_bin(c, "+").unwrap(); -/// let result: NumericValue = result.coerce_bin(d, "+").unwrap(); -/// assert_eq!(f64::try_convert(result.as_value()).unwrap(), 5.8); +/// Ok(()) +/// } +/// # Ruby::init(example).unwrap() /// ``` #[derive(Clone, Copy)] #[repr(transparent)] diff --git a/src/object.rs b/src/object.rs index 683f741f..83b95c04 100644 --- a/src/object.rs +++ b/src/object.rs @@ -25,23 +25,24 @@ pub trait Object: ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{define_module, function, prelude::*, rb_assert}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{function, prelude::*, rb_assert, Error, Ruby}; /// - /// fn example() -> i64 { + /// fn test() -> i64 { /// 42 /// } /// - /// let module = define_module("Example").unwrap(); - /// module - /// .define_singleton_method("example", function!(example, 0)) - /// .unwrap(); - /// rb_assert!("Example.example == 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::{class, define_class, function, prelude::*, rb_assert}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{function, prelude::*, rb_assert, Error, Ruby}; /// /// #[magnus::wrap(class = "Point", free_immediately, size)] /// struct Point { @@ -55,13 +56,16 @@ pub trait Object: ReprValue + Copy { /// } /// } /// - /// let class = define_class("Point", class::object()).unwrap(); - /// class - /// .define_singleton_method("new", function!(Point::new, 2)) - /// .unwrap(); + /// 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)"); /// - /// rb_assert!("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 @@ -92,22 +96,25 @@ pub trait Object: ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{eval, prelude::*, RObject}; - /// # let _cleanup = unsafe { magnus::embed::init() }; - /// - /// let val: RObject = eval( - /// r#" - /// class Example - /// def initialize(value) - /// @value = value - /// end - /// end - /// Example.new("foo") - /// "#, - /// ) - /// .unwrap(); - /// - /// assert_eq!(val.ivar_get::<_, String>("@value").unwrap(), "foo"); + /// 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 @@ -129,27 +136,30 @@ pub trait Object: ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{eval, prelude::*, rb_assert, RObject}; - /// # let _cleanup = unsafe { magnus::embed::init() }; - /// - /// let obj: RObject = eval( - /// r#" - /// class Example - /// def initialize(value) - /// @value = value - /// end - /// - /// def value - /// @value - /// end - /// end - /// Example.new("foo") - /// "#, - /// ) - /// .unwrap(); - /// - /// obj.ivar_set("@value", "bar").unwrap(); - /// rb_assert!(r#"obj.value == "bar""#, obj); + /// 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 @@ -179,10 +189,14 @@ pub trait Object: ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{prelude::*, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// assert!(ruby.str_new("example").singleton_class().is_ok()); /// - /// assert!(RString::new("example").singleton_class().is_ok()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn singleton_class(self) -> Result { protect(|| unsafe { @@ -195,21 +209,23 @@ pub trait Object: ReprValue + Copy { /// # Examples /// /// ``` - /// use magnus::{class, function, prelude::*, rb_assert, RModule, RObject}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{function, prelude::*, rb_assert, Error, RObject, Ruby}; /// - /// fn example() -> i64 { + /// fn test() -> i64 { /// 42 /// } /// - /// let module = RModule::new(); - /// module - /// .define_method("example", function!(example, 0)) - /// .unwrap(); + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let module = ruby.module_new(); + /// module.define_method("test", function!(test, 0))?; /// - /// let obj = RObject::try_convert(class::object().new_instance(()).unwrap()).unwrap(); - /// obj.extend_object(module).unwrap(); - /// rb_assert!("obj.example == 42", obj); + /// 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(|| { diff --git a/src/process.rs b/src/process.rs new file mode 100644 index 00000000..10c12d59 --- /dev/null +++ b/src/process.rs @@ -0,0 +1,147 @@ +//! 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() + } +} diff --git a/src/r_array.rs b/src/r_array.rs index 0676fb47..83ef1b61 100644 --- a/src/r_array.rs +++ b/src/r_array.rs @@ -1,21 +1,26 @@ -use std::{cmp::Ordering, fmt, os::raw::c_long, ptr::NonNull, slice}; +//! Types and functions for working with Ruby’s Array class. -#[cfg(ruby_gte_3_0)] -use rb_sys::ruby_rarray_consts::RARRAY_EMBED_LEN_SHIFT; -#[cfg(ruby_lt_3_0)] -use rb_sys::ruby_rarray_flags::RARRAY_EMBED_LEN_SHIFT; +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, ruby_rarray_flags, ruby_value_type, VALUE, + 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}, @@ -158,6 +163,49 @@ impl Ruby { 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(); @@ -169,7 +217,7 @@ impl Ruby { let mut buffer = [self.qnil().as_value(); 128]; let mut i = 0; for v in iter { - buffer[i] = self.into_value(v); + buffer[i] = self.into_value(v?); i += 1; if i >= buffer.len() { i = 0; @@ -177,7 +225,35 @@ impl Ruby { } } ary.cat(&buffer[..i]).unwrap(); - ary + 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) + } } } @@ -217,11 +293,6 @@ impl RArray { 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 _) } - } - /// Create a new empty `RArray`. /// /// # Panics @@ -232,6 +303,7 @@ impl RArray { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::RArray; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -239,9 +311,10 @@ impl RArray { /// assert!(ary.is_empty()); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -258,6 +331,7 @@ impl RArray { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::RArray; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -265,9 +339,10 @@ impl RArray { /// assert!(ary.is_empty()); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -281,38 +356,112 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{rb_assert, IntoValue, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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(1.into_value()).unwrap(); - /// rb_assert!("[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); /// - /// let ary = RArray::to_ary(vec![1, 2, 3].into_value()).unwrap(); - /// rb_assert!("[1, 2, 3] == ary", ary); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` /// /// This can fail in the case of a misbehaving `#to_ary` method: /// /// ``` - /// use magnus::{eval, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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()); /// - /// let val = eval( - /// r#" - /// o = Object.new - /// def o.to_ary - /// "not an array" - /// end - /// o - /// "#, - /// ) - /// .unwrap(); - /// 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. @@ -320,16 +469,20 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{rb_assert, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// let a = RArray::from_vec(vec![1, 2, 3]); - /// let b = a.dup(); - /// rb_assert!("a == b", a, b); - /// a.push(4).unwrap(); - /// b.push(5).unwrap(); - /// rb_assert!("a == [1, 2, 3, 4]", a); - /// rb_assert!("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 @@ -341,29 +494,22 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{eval, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, RArray, Ruby}; /// - /// let ary = RArray::new(); - /// assert_eq!(ary.len(), 0); + /// 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); /// - /// let ary: RArray = eval("[:a, :b, :c]").unwrap(); - /// assert_eq!(ary.len(), 3) + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn len(self) -> usize { debug_assert_value!(self); - unsafe { - let r_basic = self.r_basic_unchecked(); - let flags = r_basic.as_ref().flags; - if (flags & ruby_rarray_flags::RARRAY_EMBED_FLAG as VALUE) != 0 { - let len = (flags >> RARRAY_EMBED_LEN_SHIFT as VALUE) - & (ruby_rarray_flags::RARRAY_EMBED_LEN_MASK as VALUE - >> RARRAY_EMBED_LEN_SHIFT as VALUE); - len.try_into().unwrap() - } else { - self.as_internal().as_ref().as_.heap.len.try_into().unwrap() - } - } + unsafe { RARRAY_LEN(self.as_rb_value()) as _ } } /// Return whether self contains any entries or not. @@ -371,14 +517,18 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::RArray; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let ary = RArray::new(); - /// assert!(ary.is_empty()); + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let ary = ruby.ary_new(); + /// assert!(ary.is_empty()); /// - /// ary.push("foo").unwrap(); - /// 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 @@ -389,17 +539,21 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{eval, value::qnil, RArray, Symbol}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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())); /// - /// let ary: RArray = eval(r#"[:foo, "bar", 2]"#).unwrap(); - /// assert!(ary.includes(Symbol::new("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(qnil())); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn includes(self, val: T) -> bool where @@ -416,27 +570,37 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{prelude::*, rb_assert, value::qnil, Integer, RArray, Symbol}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, rb_assert, Error, Ruby}; /// - /// let ary = RArray::new(); - /// ary.cat(&[ - /// Symbol::new("a").as_value(), - /// Integer::from_i64(1).as_value(), - /// qnil().as_value(), - /// ]) - /// .unwrap(); - /// rb_assert!("ary == [:a, 1, nil]", ary); + /// 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, RArray, Symbol}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{rb_assert, Error, Ruby}; /// - /// let ary = RArray::new(); - /// ary.cat(&[Symbol::new("a"), Symbol::new("b"), Symbol::new("c")]) - /// .unwrap(); - /// rb_assert!("ary == [:a, :b, :c]", ary); + /// 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 @@ -454,14 +618,18 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{rb_assert, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// let a = RArray::from_vec(vec![1, 2, 3]); - /// let b = RArray::from_vec(vec!["a", "b", "c"]); - /// a.concat(b).unwrap(); - /// rb_assert!(r#"a == [1, 2, 3, "a", "b", "c"]"#, a); - /// rb_assert!(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())) })?; @@ -474,15 +642,19 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{rb_assert, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// let a = RArray::from_vec(vec![1, 2, 3]); - /// let b = RArray::from_vec(vec!["a", "b", "c"]); - /// let c = a.plus(b); - /// rb_assert!(r#"c == [1, 2, 3, "a", "b", "c"]"#, c); - /// rb_assert!(r#"a == [1, 2, 3]"#, a); - /// rb_assert!(r#"b == ["a", "b", "c"]"#, b); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn plus(self, other: Self) -> Self { unsafe { @@ -500,6 +672,7 @@ impl RArray { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{prelude::*, rb_assert, value::qnil, Integer, RArray, Symbol}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -512,6 +685,7 @@ impl RArray { /// ``` /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{rb_assert, RArray, Symbol}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -519,9 +693,10 @@ impl RArray { /// rb_assert!("ary == [:a, :b, :c]", ary); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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 @@ -537,14 +712,18 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{rb_assert, RArray, Symbol}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{rb_assert, Error, Ruby}; /// - /// let ary = RArray::new(); - /// ary.push(Symbol::new("a")).unwrap(); - /// ary.push(1).unwrap(); - /// ary.push(()).unwrap(); - /// rb_assert!("ary == [:a, 1, nil]", ary); + /// 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 @@ -562,25 +741,33 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{eval, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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()); /// - /// let ary: RArray = eval("[1, 2, 3]").unwrap(); - /// assert_eq!(ary.pop::().unwrap(), 3); - /// assert_eq!(ary.pop::().unwrap(), 2); - /// assert_eq!(ary.pop::().unwrap(), 1); - /// assert!(ary.pop::().is_err()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` /// /// ``` - /// use magnus::{eval, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, RArray, Ruby}; /// - /// let ary: RArray = eval("[1, 2, 3]").unwrap(); - /// assert_eq!(ary.pop::>().unwrap(), Some(3)); - /// assert_eq!(ary.pop::>().unwrap(), Some(2)); - /// assert_eq!(ary.pop::>().unwrap(), Some(1)); - /// assert_eq!(ary.pop::>().unwrap(), None); + /// 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 @@ -597,14 +784,18 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{rb_assert, RArray, Symbol}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{rb_assert, Error, Ruby}; /// - /// let ary = RArray::new(); - /// ary.unshift(Symbol::new("a")).unwrap(); - /// ary.unshift(1).unwrap(); - /// ary.unshift(()).unwrap(); - /// rb_assert!("ary == [nil, 1, :a]", ary); + /// 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 @@ -622,25 +813,33 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{eval, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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()); /// - /// let ary: RArray = eval("[1, 2, 3]").unwrap(); - /// assert_eq!(ary.shift::().unwrap(), 1); - /// assert_eq!(ary.shift::().unwrap(), 2); - /// assert_eq!(ary.shift::().unwrap(), 3); - /// assert!(ary.shift::().is_err()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` /// /// ``` - /// use magnus::{eval, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, RArray, Ruby}; /// - /// let ary: RArray = eval("[1, 2, 3]").unwrap(); - /// assert_eq!(ary.shift::>().unwrap(), Some(1)); - /// assert_eq!(ary.shift::>().unwrap(), Some(2)); - /// assert_eq!(ary.shift::>().unwrap(), Some(3)); - /// assert_eq!(ary.shift::>().unwrap(), None); + /// 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 @@ -657,12 +856,16 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{rb_assert, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{rb_assert, Error, Ruby}; /// - /// let ary = RArray::from_vec(vec![1, 1, 2, 3]); - /// ary.delete(1).unwrap(); - /// rb_assert!("ary == [2, 3]", ary); + /// 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 @@ -688,13 +891,17 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{rb_assert, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{rb_assert, Error, Ruby}; /// - /// let ary = RArray::from_vec(vec!["a", "b", "c"]); - /// let removed: Option = ary.delete_at(1).unwrap(); - /// assert_eq!(removed, Some(String::from("b"))); - /// rb_assert!(r#"ary == ["a", "c"]"#, ary); + /// 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 @@ -711,13 +918,17 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::RArray; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let ary = RArray::from_vec::(vec![1, 2, 3]); - /// assert!(!ary.is_empty()); - /// ary.clear().unwrap(); - /// assert!(ary.is_empty()); + /// 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())) })?; @@ -734,14 +945,18 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{rb_assert, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// let ary = RArray::from_vec::(vec![1, 2, 3]); - /// ary.resize(5).unwrap(); - /// rb_assert!("ary == [1, 2, 3, nil, nil]", ary); - /// ary.resize(2).unwrap(); - /// rb_assert!("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)) })?; @@ -755,12 +970,16 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{rb_assert, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// let ary = RArray::from_vec::(vec![1, 2, 3]); - /// ary.reverse().unwrap(); - /// rb_assert!("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())) })?; @@ -777,21 +996,29 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{rb_assert, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// let ary = RArray::from_vec::(vec![1, 2, 3, 4, 5, 6, 7]); - /// ary.rotate(3).unwrap(); - /// rb_assert!("ary == [4, 5, 6, 7, 1, 2, 3]", ary); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` /// /// ``` - /// use magnus::{rb_assert, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{rb_assert, Error, Ruby}; /// - /// let ary = RArray::from_vec::(vec![1, 2, 3, 4, 5, 6, 7]); - /// ary.rotate(-3).unwrap(); - /// rb_assert!("ary == [5, 6, 7, 1, 2, 3, 4]", ary); + /// 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)) })?; @@ -805,12 +1032,16 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{rb_assert, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{rb_assert, Error, Ruby}; /// - /// let ary = RArray::from_vec::(vec![2, 1, 3]); - /// ary.sort().unwrap(); - /// rb_assert!("ary == [1, 2, 3]", ary); + /// 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())) })?; @@ -827,6 +1058,7 @@ impl RArray { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{rb_assert, RArray}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -834,9 +1066,10 @@ impl RArray { /// rb_assert!("ary == [1, 2, 3]", ary); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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 @@ -859,16 +1092,20 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{eval, rb_assert, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); + /// } /// - /// let ary: RArray = eval("[1, 2, 3, 4, 5]").unwrap(); - /// // 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 = RArray::from_slice(&ary.as_slice()[1..4]); - /// rb_assert!("middle == [2, 3, 4]", middle); + /// Ok(()) /// } + /// # Ruby::init(example).unwrap() /// ``` pub unsafe fn as_slice(&self) -> &[Value] { self.as_slice_unconstrained() @@ -876,20 +1113,10 @@ impl RArray { pub(crate) 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 & ruby_rarray_flags::RARRAY_EMBED_FLAG as VALUE) != 0 { - let len = (flags >> RARRAY_EMBED_LEN_SHIFT as VALUE) - & (ruby_rarray_flags::RARRAY_EMBED_LEN_MASK as VALUE - >> RARRAY_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) - } + 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 @@ -904,11 +1131,15 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{eval, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, RArray, Ruby}; /// - /// let ary: RArray = eval("[1, 2, 3]").unwrap(); - /// assert_eq!(ary.to_vec::().unwrap(), vec![1, 2, 3]); + /// 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 @@ -924,13 +1155,17 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{eval, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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()); /// - /// let ary: RArray = eval("[1, 2, 3]").unwrap(); - /// 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 { @@ -951,13 +1186,17 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{eval, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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()); /// - /// let ary: RArray = eval("[1, 2, 3]").unwrap(); - /// assert_eq!(ary.to_array::().unwrap(), [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 @@ -987,15 +1226,19 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{prelude::*, value::qnil, Integer, RArray, Symbol}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, Ruby}; /// - /// let ary = RArray::from_slice(&[ - /// Symbol::new("a").as_value(), - /// Integer::from_i64(1).as_value(), - /// qnil().as_value(), - /// ]); - /// assert_eq!(ary.join(", ").unwrap().to_string().unwrap(), "a, 1, ") + /// 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 @@ -1016,23 +1259,24 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{eval, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, RArray, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let ary: RArray = ruby.eval(r#"["a", "b", "c"]"#)?; /// - /// let ary: RArray = eval(r#"["a", "b", "c"]"#).unwrap(); + /// 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_eq!(ary.entry::(0).unwrap(), String::from("a")); - /// assert_eq!(ary.entry::(0).unwrap(), 'a'); - /// assert_eq!( - /// ary.entry::>(0).unwrap(), - /// Some(String::from("a")) - /// ); - /// assert_eq!(ary.entry::(1).unwrap(), String::from("b")); - /// assert_eq!(ary.entry::(-1).unwrap(), String::from("c")); - /// assert_eq!(ary.entry::>(3).unwrap(), None); + /// assert!(ary.entry::(0).is_err()); + /// assert!(ary.entry::(3).is_err()); /// - /// 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 @@ -1056,15 +1300,23 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{rb_assert, RArray, Symbol}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{rb_assert, Error, Ruby}; /// - /// let ary = RArray::from_slice(&[Symbol::new("a"), Symbol::new("b"), Symbol::new("c")]); - /// ary.store(0, Symbol::new("d")).unwrap(); - /// ary.store(5, Symbol::new("e")).unwrap(); - /// ary.store(6, Symbol::new("f")).unwrap(); - /// ary.store(-1, Symbol::new("g")).unwrap(); - /// rb_assert!("ary == [:d, :b, :c, nil, nil, :e, :g]", ary); + /// 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 @@ -1088,11 +1340,16 @@ impl 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", ()) @@ -1111,17 +1368,21 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::RArray; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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)); /// - /// let ary = RArray::from_vec((0..256).collect()); - /// let copy = RArray::new(); - /// copy.replace(ary).unwrap(); - /// assert!(ary.is_shared(copy)); - /// assert!(copy.is_shared(ary)); - /// copy.push(11).unwrap(); - /// assert!(!ary.is_shared(copy)); - /// assert!(!copy.is_shared(ary)); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn is_shared(self, other: Self) -> bool { unsafe { @@ -1147,15 +1408,19 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::RArray; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let ary = RArray::from_vec((0..256).collect()); - /// let copy = RArray::new(); - /// copy.replace(ary).unwrap(); - /// assert!(copy.is_shared(ary)); - /// copy.push(11).unwrap(); - /// assert!(!copy.is_shared(ary)); + /// 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())) })?; @@ -1170,14 +1435,18 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::{rb_assert, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// let ary = RArray::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!("a == [1, 2, 3, 4, 5]", a); - /// rb_assert!("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 { @@ -1200,15 +1469,19 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::RArray; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// let ary = RArray::from_vec(vec![("foo", 1), ("bar", 2), ("baz", 3), ("baz", 4)]); - /// assert_eq!( - /// ary.assoc::<_, (String, i64)>("baz").unwrap(), - /// (String::from("baz"), 3) - /// ); - /// assert_eq!(ary.assoc::<_, Option<(String, i64)>>("quz").unwrap(), None); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn assoc(self, key: K) -> Result where @@ -1229,15 +1502,16 @@ impl RArray { /// # Examples /// /// ``` - /// use magnus::RArray; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let ary = RArray::from_vec(vec![("foo", 1), ("bar", 2), ("baz", 3), ("qux", 3)]); - /// assert_eq!( - /// ary.rassoc::<_, (String, i64)>(3).unwrap(), - /// (String::from("baz"), 3) - /// ); - /// assert_eq!(ary.rassoc::<_, Option<(String, i64)>>(4).unwrap(), None); + /// 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 @@ -1261,22 +1535,26 @@ impl RArray { /// ``` /// use std::cmp::Ordering; /// - /// use magnus::RArray; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let a = RArray::from_vec(vec![1, 2, 3]); - /// let b = RArray::from_vec(vec![1, 2, 3]); - /// assert_eq!(a.cmp(b).unwrap(), Some(Ordering::Equal)); + /// 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 = RArray::from_vec(vec![1, 2, 0]); - /// assert_eq!(a.cmp(c).unwrap(), Some(Ordering::Greater)); + /// let c = ruby.ary_from_vec(vec![1, 2, 0]); + /// assert_eq!(a.cmp(c)?, Some(Ordering::Greater)); /// - /// let d = RArray::from_vec(vec![1, 2, 4]); - /// assert_eq!(a.cmp(d).unwrap(), Some(Ordering::Less)); + /// let d = ruby.ary_from_vec(vec![1, 2, 4]); + /// assert_eq!(a.cmp(d)?, Some(Ordering::Less)); /// - /// let e = RArray::from_vec(vec![1, 2]); - /// e.push(()).unwrap(); - /// assert_eq!(a.cmp(e).unwrap(), None); + /// 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 @@ -1307,6 +1585,55 @@ impl fmt::Debug for RArray { } } +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 { @@ -1314,466 +1641,375 @@ impl IntoValue for RArray { } } -impl IntoValue for (T0,) -where - T0: IntoValue, -{ - fn into_value_with(self, handle: &Ruby) -> Value { - let ary = [handle.into_value(self.0)]; - handle.ary_new_from_values(&ary).into_value_with(handle) +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,)* {} + }); } } -unsafe impl IntoValueFromNative for (T0,) where T0: IntoValueFromNative {} +seq!(N in 0..12 { + impl_into_value!(N); +}); -impl IntoValue for (T0, T1) +impl IntoValue for Vec where - T0: IntoValue, - T1: IntoValue, + T: IntoValueFromNative, { + #[inline] fn into_value_with(self, handle: &Ruby) -> Value { - let ary = [handle.into_value(self.0), handle.into_value(self.1)]; - handle.ary_new_from_values(&ary).into_value_with(handle) + handle.ary_from_vec(self).into_value_with(handle) } } -unsafe impl IntoValueFromNative for (T0, T1) -where - T0: IntoValueFromNative, - T1: IntoValueFromNative, -{ -} +unsafe impl IntoValueFromNative for Vec where T: IntoValueFromNative {} -impl IntoValue for (T0, T1, T2) +#[cfg(feature = "old-api")] +impl FromIterator for RArray where - T0: IntoValue, - T1: IntoValue, - T2: IntoValue, + T: IntoValue, { - fn into_value_with(self, handle: &Ruby) -> Value { - let ary = [ - handle.into_value(self.0), - handle.into_value(self.1), - handle.into_value(self.2), - ]; - handle.ary_new_from_values(&ary).into_value_with(handle) + /// 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) } } -unsafe impl IntoValueFromNative for (T0, T1, T2) -where - T0: IntoValueFromNative, - T1: IntoValueFromNative, - T2: IntoValueFromNative, -{ -} +impl Object for RArray {} -impl IntoValue for (T0, T1, T2, T3) -where - T0: IntoValue, - T1: IntoValue, - T2: IntoValue, - T3: IntoValue, -{ - fn into_value_with(self, handle: &Ruby) -> Value { - let ary = [ - handle.into_value(self.0), - handle.into_value(self.1), - handle.into_value(self.2), - handle.into_value(self.3), - ]; - handle.ary_new_from_values(&ary).into_value_with(handle) +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()), + ) + }) + }) + } } } -unsafe impl IntoValueFromNative for (T0, T1, T2, T3) -where - T0: IntoValueFromNative, - T1: IntoValueFromNative, - T2: IntoValueFromNative, - T3: IntoValueFromNative, -{ +/// 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 IntoValue for (T0, T1, T2, T3, T4) -where - T0: IntoValue, - T1: IntoValue, - T2: IntoValue, - T3: IntoValue, - T4: IntoValue, -{ - fn into_value_with(self, handle: &Ruby) -> Value { - let ary = [ - handle.into_value(self.0), - handle.into_value(self.1), - handle.into_value(self.2), - handle.into_value(self.3), - handle.into_value(self.4), - ]; - handle.ary_new_from_values(&ary).into_value_with(handle) +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) + } } -} -unsafe impl IntoValueFromNative for (T0, T1, T2, T3, T4) -where - T0: IntoValueFromNative, - T1: IntoValueFromNative, - T2: IntoValueFromNative, - T3: IntoValueFromNative, - T4: IntoValueFromNative, -{ -} + 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>); -impl IntoValue for (T0, T1, T2, T3, T4, T5) -where - T0: IntoValue, - T1: IntoValue, - T2: IntoValue, - T3: IntoValue, - T4: IntoValue, - T5: IntoValue, -{ - fn into_value_with(self, handle: &Ruby) -> Value { - let ary = [ - handle.into_value(self.0), - handle.into_value(self.1), - handle.into_value(self.2), - handle.into_value(self.3), - handle.into_value(self.4), - handle.into_value(self.5), - ]; - handle.ary_new_from_values(&ary).into_value_with(handle) + /// 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) + } } -} -unsafe impl IntoValueFromNative for (T0, T1, T2, T3, T4, T5) -where - T0: IntoValueFromNative, - T1: IntoValueFromNative, - T2: IntoValueFromNative, - T3: IntoValueFromNative, - T4: IntoValueFromNative, - T5: IntoValueFromNative, -{ -} + /// 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())) + } + } -impl IntoValue for (T0, T1, T2, T3, T4, T5, T6) -where - T0: IntoValue, - T1: IntoValue, - T2: IntoValue, - T3: IntoValue, - T4: IntoValue, - T5: IntoValue, - T6: IntoValue, -{ - fn into_value_with(self, handle: &Ruby) -> Value { - let ary = [ - handle.into_value(self.0), - handle.into_value(self.1), - handle.into_value(self.2), - handle.into_value(self.3), - handle.into_value(self.4), - handle.into_value(self.5), - handle.into_value(self.6), - ]; - handle.ary_new_from_values(&ary).into_value_with(handle) + /// 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) + } } -} -unsafe impl IntoValueFromNative for (T0, T1, T2, T3, T4, T5, T6) -where - T0: IntoValueFromNative, - T1: IntoValueFromNative, - T2: IntoValueFromNative, - T3: IntoValueFromNative, - T4: IntoValueFromNative, - T5: IntoValueFromNative, - T6: IntoValueFromNative, -{ -} + /// See [`RArray::as_slice`]. + pub unsafe fn as_slice(&self) -> &[Value] { + RArray::from_value_unchecked(self.0.get()).as_slice_unconstrained() + } -impl IntoValue for (T0, T1, T2, T3, T4, T5, T6, T7) -where - T0: IntoValue, - T1: IntoValue, - T2: IntoValue, - T3: IntoValue, - T4: IntoValue, - T5: IntoValue, - T6: IntoValue, - T7: IntoValue, -{ - fn into_value_with(self, handle: &Ruby) -> Value { - let ary = [ - handle.into_value(self.0), - handle.into_value(self.1), - handle.into_value(self.2), - handle.into_value(self.3), - handle.into_value(self.4), - handle.into_value(self.5), - handle.into_value(self.6), - handle.into_value(self.7), - ]; - handle.ary_new_from_values(&ary).into_value_with(handle) + /// 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() } } -} -unsafe impl IntoValueFromNative for (T0, T1, T2, T3, T4, T5, T6, T7) -where - T0: IntoValueFromNative, - T1: IntoValueFromNative, - T2: IntoValueFromNative, - T3: IntoValueFromNative, - T4: IntoValueFromNative, - T5: IntoValueFromNative, - T6: IntoValueFromNative, - T7: IntoValueFromNative, -{ -} + /// See [`RArray::join`]. + pub fn join(&self, sep: S) -> Result + where + S: IntoRString, + { + unsafe { RArray::from_value_unchecked(self.0.get()).join(sep) } + } -impl IntoValue for (T0, T1, T2, T3, T4, T5, T6, T7, T8) -where - T0: IntoValue, - T1: IntoValue, - T2: IntoValue, - T3: IntoValue, - T4: IntoValue, - T5: IntoValue, - T6: IntoValue, - T7: IntoValue, - T8: IntoValue, -{ - fn into_value_with(self, handle: &Ruby) -> Value { - let ary = [ - handle.into_value(self.0), - handle.into_value(self.1), - handle.into_value(self.2), - handle.into_value(self.3), - handle.into_value(self.4), - handle.into_value(self.5), - handle.into_value(self.6), - handle.into_value(self.7), - handle.into_value(self.8), - ]; - handle.ary_new_from_values(&ary).into_value_with(handle) + // 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())) + } } } -unsafe impl IntoValueFromNative - for (T0, T1, T2, T3, T4, T5, T6, T7, T8) +impl TypedArray where - T0: IntoValueFromNative, - T1: IntoValueFromNative, - T2: IntoValueFromNative, - T3: IntoValueFromNative, - T4: IntoValueFromNative, - T5: IntoValueFromNative, - T6: IntoValueFromNative, - T7: IntoValueFromNative, - T8: IntoValueFromNative, + 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 IntoValue for (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) +impl TypedArray where - T0: IntoValue, - T1: IntoValue, - T2: IntoValue, - T3: IntoValue, - T4: IntoValue, - T5: IntoValue, - T6: IntoValue, - T7: IntoValue, - T8: IntoValue, - T9: IntoValue, + T: ReprValue, { - fn into_value_with(self, handle: &Ruby) -> Value { - let ary = [ - handle.into_value(self.0), - handle.into_value(self.1), - handle.into_value(self.2), - handle.into_value(self.3), - handle.into_value(self.4), - handle.into_value(self.5), - handle.into_value(self.6), - handle.into_value(self.7), - handle.into_value(self.8), - handle.into_value(self.9), - ]; - handle.ary_new_from_values(&ary).into_value_with(handle) - } + proxy!(cat(s: &[T]) -> Result<(), Error>); } -unsafe impl IntoValueFromNative - for (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) +impl TypedArray where - T0: IntoValueFromNative, - T1: IntoValueFromNative, - T2: IntoValueFromNative, - T3: IntoValueFromNative, - T4: IntoValueFromNative, - T5: IntoValueFromNative, - T6: IntoValueFromNative, - T7: IntoValueFromNative, - T8: IntoValueFromNative, - T9: IntoValueFromNative, + 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 IntoValue - for (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) +impl TypedArray where - T0: IntoValue, - T1: IntoValue, - T2: IntoValue, - T3: IntoValue, - T4: IntoValue, - T5: IntoValue, - T6: IntoValue, - T7: IntoValue, - T8: IntoValue, - T9: IntoValue, - T10: IntoValue, + T: TryConvertOwned, { - fn into_value_with(self, handle: &Ruby) -> Value { - let ary = [ - handle.into_value(self.0), - handle.into_value(self.1), - handle.into_value(self.2), - handle.into_value(self.3), - handle.into_value(self.4), - handle.into_value(self.5), - handle.into_value(self.6), - handle.into_value(self.7), - handle.into_value(self.8), - handle.into_value(self.9), - handle.into_value(self.10), - ]; - handle.ary_new_from_values(&ary).into_value_with(handle) + /// See [`RArray::to_vec`]. + pub fn to_vec(&self) -> Vec { + unsafe { RArray::from_value_unchecked(self.0.get()).to_vec().unwrap() } } } -unsafe impl IntoValueFromNative - for (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) +impl IntoIterator for TypedArray where - T0: IntoValueFromNative, - T1: IntoValueFromNative, - T2: IntoValueFromNative, - T3: IntoValueFromNative, - T4: IntoValueFromNative, - T5: IntoValueFromNative, - T6: IntoValueFromNative, - T7: IntoValueFromNative, - T8: IntoValueFromNative, - T9: IntoValueFromNative, - T10: IntoValueFromNative, + 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 (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) +impl IntoValue for TypedArray where - T0: IntoValue, - T1: IntoValue, - T2: IntoValue, - T3: IntoValue, - T4: IntoValue, - T5: IntoValue, - T6: IntoValue, - T7: IntoValue, - T8: IntoValue, - T9: IntoValue, - T10: IntoValue, - T11: IntoValue, + T: IntoValue, { - fn into_value_with(self, handle: &Ruby) -> Value { - let ary = [ - handle.into_value(self.0), - handle.into_value(self.1), - handle.into_value(self.2), - handle.into_value(self.3), - handle.into_value(self.4), - handle.into_value(self.5), - handle.into_value(self.6), - handle.into_value(self.7), - handle.into_value(self.8), - handle.into_value(self.9), - handle.into_value(self.10), - handle.into_value(self.11), - ]; - handle.ary_new_from_values(&ary).into_value_with(handle) + fn into_value_with(self, _: &Ruby) -> Value { + self.to_r_array().as_value() } } -unsafe impl IntoValueFromNative - for (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) -where - T0: IntoValueFromNative, - T1: IntoValueFromNative, - T2: IntoValueFromNative, - T3: IntoValueFromNative, - T4: IntoValueFromNative, - T5: IntoValueFromNative, - T6: IntoValueFromNative, - T7: IntoValueFromNative, - T8: IntoValueFromNative, - T9: IntoValueFromNative, - T10: IntoValueFromNative, - T11: IntoValueFromNative, -{ +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 IntoValue for Vec +impl Iterator for Iter where - T: IntoValueFromNative, + T: TryConvert, { + type Item = T; + #[inline] - fn into_value_with(self, handle: &Ruby) -> Value { - handle.ary_from_vec(self).into_value_with(handle) + 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 + } } -} -#[cfg(feature = "friendly-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) + fn size_hint(&self) -> (usize, Option) { + let remaining = self.len - self.idx; + (remaining, Some(remaining)) } } -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()), - ) - }) - }) +impl Iter { + fn new(data: RArray) -> Self { + Self { + data, + len: data.len(), + idx: 0, + item_type: PhantomData, } } } diff --git a/src/r_bignum.rs b/src/r_bignum.rs index 9a0d6a8d..3e28ee42 100644 --- a/src/r_bignum.rs +++ b/src/r_bignum.rs @@ -136,6 +136,7 @@ impl RBignum { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::RBignum; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -145,9 +146,10 @@ impl RBignum { /// assert!(RBignum::from_i64(0).is_err()); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -166,6 +168,7 @@ impl RBignum { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::RBignum; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -174,9 +177,10 @@ impl RBignum { /// assert!(RBignum::from_u64(0).is_err()); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -402,14 +406,18 @@ impl RBignum { /// # Examples /// /// ``` - /// use magnus::RBignum; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let num = ruby.bignum_from_u64(4611686018427387904).unwrap(); + /// assert!(num.is_positive()); /// - /// let num = RBignum::from_u64(4611686018427387904).unwrap(); - /// assert!(num.is_positive()); + /// let num = ruby.bignum_from_i64(-4611686018427387905).unwrap(); + /// assert!(!num.is_positive()); /// - /// let num = RBignum::from_i64(-4611686018427387905).unwrap(); - /// assert!(!num.is_positive()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn is_positive(self) -> bool { debug_assert_value!(self); @@ -424,14 +432,18 @@ impl RBignum { /// # Examples /// /// ``` - /// use magnus::RBignum; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let num = ruby.bignum_from_i64(-4611686018427387905).unwrap(); + /// assert!(num.is_negative()); /// - /// let num = RBignum::from_i64(-4611686018427387905).unwrap(); - /// assert!(num.is_negative()); + /// let num = ruby.bignum_from_u64(4611686018427387904).unwrap(); + /// assert!(!num.is_negative()); /// - /// let num = RBignum::from_u64(4611686018427387904).unwrap(); - /// assert!(!num.is_negative()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn is_negative(self) -> bool { !self.is_positive() diff --git a/src/r_complex.rs b/src/r_complex.rs index 39503442..f2060544 100644 --- a/src/r_complex.rs +++ b/src/r_complex.rs @@ -56,11 +56,15 @@ impl RComplex { /// # Examples /// /// ``` - /// use magnus::{Integer, RComplex}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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"); /// - /// let complex = RComplex::new(Integer::from_i64(2), 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 @@ -80,14 +84,18 @@ impl RComplex { /// # Examples /// /// ``` - /// use magnus::{Integer, RComplex}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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" + /// ); /// - /// let complex = RComplex::polar(Integer::from_i64(2), Integer::from_i64(3)).unwrap(); - /// assert_eq!( - /// complex.to_string(), - /// "-1.9799849932008908+0.2822400161197344i" - /// ); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn polar(real: T, imag: U) -> Result where @@ -107,11 +115,15 @@ impl RComplex { /// # Examples /// /// ``` - /// use magnus::{Integer, RComplex}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// let complex = RComplex::new(Integer::from_i64(9), Integer::from_i64(-4)); - /// assert_eq!(complex.real::().unwrap(), 9); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn real(self) -> Result where @@ -126,11 +138,15 @@ impl RComplex { /// # Examples /// /// ``` - /// use magnus::{Integer, RComplex}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, RComplex, Ruby}; /// - /// let complex = RComplex::new(Integer::from_i64(9), Integer::from_i64(-4)); - /// assert_eq!(complex.imag::().unwrap(), -4); + /// 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 @@ -145,11 +161,15 @@ impl RComplex { /// # Examples /// /// ``` - /// use magnus::{Integer, RComplex}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, RComplex, Ruby}; /// - /// let complex = RComplex::new(Integer::from_i64(1), Integer::from_i64(2)); - /// assert_eq!(complex.conjugate().to_string(), "1-2i"); + /// 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())) } @@ -160,11 +180,15 @@ impl RComplex { /// # Examples /// /// ``` - /// use magnus::{Integer, RComplex}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, RComplex, Ruby}; /// - /// let complex = RComplex::new(Integer::from_i64(3), Integer::from_i64(-4)); - /// assert_eq!(complex.abs(), 5.0); + /// 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() } @@ -177,11 +201,15 @@ impl RComplex { /// ``` /// use std::f64::consts::PI; /// - /// use magnus::{Float, Integer, RComplex}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// let complex = RComplex::polar(Integer::from_i64(3), Float::from_f64(PI / 2.0)).unwrap(); - /// 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() } diff --git a/src/r_file.rs b/src/r_file.rs index 3076e12c..7ce07857 100644 --- a/src/r_file.rs +++ b/src/r_file.rs @@ -1,5 +1,9 @@ use 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::{ @@ -14,7 +18,7 @@ use crate::{ Ruby, }; -/// A Value pointer to a RFile struct, Ruby's internal representation of files. +/// 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. @@ -24,6 +28,22 @@ 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 { @@ -31,6 +51,12 @@ impl RFile { .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 { @@ -70,3 +96,28 @@ impl TryConvert for RFile { }) } } + +#[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 } + } +} diff --git a/src/r_float.rs b/src/r_float.rs index 89301de2..29c5e5c5 100644 --- a/src/r_float.rs +++ b/src/r_float.rs @@ -130,6 +130,7 @@ impl RFloat { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{rb_assert, RFloat}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -140,9 +141,10 @@ impl RFloat { /// assert!(RFloat::from_f64(1.7272337110188893e-77).is_err()); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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 { @@ -159,6 +161,7 @@ impl RFloat { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{rb_assert, RFloat}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -169,9 +172,10 @@ impl RFloat { /// rb_assert!("f == 1.7272337110188890e-77", f); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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 { diff --git a/src/r_hash.rs b/src/r_hash.rs index dcf65835..fe55cf3b 100644 --- a/src/r_hash.rs +++ b/src/r_hash.rs @@ -2,20 +2,19 @@ use std::{ collections::HashMap, + convert::Infallible, fmt, hash::Hash, os::raw::{c_int, c_long}, panic::AssertUnwindSafe, }; -#[cfg(ruby_gte_2_7)] -use rb_sys::rb_hash_bulk_insert; #[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_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, + 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::{ @@ -125,6 +124,112 @@ impl Ruby { 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 @@ -173,6 +278,7 @@ impl RHash { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::RHash; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -180,9 +286,10 @@ impl RHash { /// assert!(hash.is_empty()); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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() @@ -199,6 +306,7 @@ impl RHash { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::RHash; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -206,9 +314,10 @@ impl RHash { /// assert!(ary.is_empty()); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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] @@ -223,12 +332,16 @@ impl RHash { /// # Examples /// /// ``` - /// use magnus::{rb_assert, RHash}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{rb_assert, Error, Ruby}; /// - /// let hash = RHash::new(); - /// hash.aset("answer", 42).unwrap(); - /// rb_assert!(r#"hash == {"answer" => 42}"#, hash); + /// 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 @@ -255,24 +368,27 @@ impl RHash { /// # Examples /// /// ``` - /// use magnus::{prelude::*, rb_assert, RHash, RString, Symbol}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, rb_assert, Error, Ruby}; /// - /// let hash = RHash::new(); - /// hash.bulk_insert(&[ - /// Symbol::new("given_name").as_value(), - /// RString::new("Arthur").as_value(), - /// Symbol::new("family_name").as_value(), - /// RString::new("Dent").as_value(), - /// ]) - /// .unwrap(); - /// rb_assert!( - /// r#"hash == {given_name: "Arthur", family_name: "Dent"}"#, - /// hash, - /// ); - /// ``` - #[cfg(any(ruby_gte_2_7, docsrs))] - #[cfg_attr(docsrs, doc(cfg(ruby_gte_2_7)))] + /// 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, @@ -290,18 +406,22 @@ impl RHash { /// # Examples /// /// ``` - /// use magnus::{eval, rb_assert, RHash}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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)?; /// - /// let a: RHash = eval("{a: 1, b: 2}").unwrap(); - /// let b: RHash = eval("{b: 3, c: 4}").unwrap(); - /// a.update(b).unwrap(); + /// // a is mutated, in case of conflicts b wins + /// rb_assert!(ruby, "a == {a: 1, b: 3, c: 4}", a); /// - /// // a is mutated, in case of conflicts b wins - /// rb_assert!("a == {a: 1, b: 3, c: 4}", a); + /// // b is unmodified + /// rb_assert!(ruby, "b == {b: 3, c: 4}", b); /// - /// // b is unmodified - /// rb_assert!("b == {b: 3, c: 4}", b); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` // // Implementation note: `rb_hash_update_by` takes a third optional argument, @@ -319,38 +439,45 @@ impl RHash { /// Return the value for `key`, converting it to `U`. /// /// Returns hash's default if `key` is missing. See also - /// [`lookup`](RHash::lookup), [`get`](RHash::get), and - /// [`fetch`](RHash::fetch). + /// [`lookup`](RHash::lookup), [`lookup2`](RHash::lookup2), + /// [`get`](RHash::get), and [`fetch`](RHash::fetch). /// /// # Examples /// /// ``` - /// use magnus::{value::Qnil, RHash}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{value::Qnil, Error, Ruby}; /// - /// let hash = RHash::new(); - /// hash.aset("answer", 42).unwrap(); - /// assert_eq!(hash.aref::<_, i64>("answer").unwrap(), 42); - /// assert!(hash.aref::<_, Qnil>("missing").is_ok()); - /// assert_eq!(hash.aref::<_, Option>("answer").unwrap(), Some(42)); - /// assert_eq!(hash.aref::<_, Option>("missing").unwrap(), None); + /// 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::{eval, RHash}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, RHash, Ruby}; /// - /// let hash: RHash = eval( - /// r#" - /// hash = {"answer" => 42} - /// hash.default = 0 - /// hash - /// "#, - /// ) - /// .unwrap(); - /// assert_eq!(hash.aref::<_, i64>("answer").unwrap(), 42); - /// assert_eq!(hash.aref::<_, i64>("missing").unwrap(), 0); - /// assert_eq!(hash.aref::<_, i64>(()).unwrap(), 0); + /// 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 @@ -365,26 +492,30 @@ impl RHash { /// Return the value for `key`, converting it to `U`. /// /// Returns `nil` if `key` is missing. See also [`aref`](RHash::aref), - /// [`get`](RHash::get), and [`fetch`](RHash::fetch). + /// [`lookup2`](RHash::lookup2), [`get`](RHash::get), and + /// [`fetch`](RHash::fetch). /// /// # Examples /// /// ``` - /// use magnus::{eval, value::Qnil, RHash}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// let hash: RHash = eval( - /// r#" - /// hash = {"answer" => 42} - /// hash.default = 0 - /// hash - /// "#, - /// ) - /// .unwrap(); - /// assert_eq!(hash.lookup::<_, i64>("answer").unwrap(), 42); - /// assert!(hash.lookup::<_, Qnil>("missing").is_ok()); - /// assert_eq!(hash.lookup::<_, Option>("answer").unwrap(), Some(42)); - /// assert_eq!(hash.lookup::<_, Option>("missing").unwrap(), None); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn lookup(self, key: T) -> Result where @@ -396,10 +527,58 @@ impl RHash { .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), and [`fetch`](RHash::fetch). + /// [`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 @@ -408,13 +587,17 @@ impl RHash { /// # Examples /// /// ``` - /// use magnus::RHash; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let hash = RHash::new(); - /// hash.aset("answer", 42).unwrap(); - /// assert!(hash.get("answer").is_some()); - /// assert!(hash.get("missing").is_none()); + /// 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 @@ -435,26 +618,30 @@ impl RHash { /// Return the value for `key`, converting it to `U`. /// /// Returns `Err` if `key` is missing. See also [`aref`](RHash::aref), - /// [`lookup`](RHash::lookup), and [`get`](RHash::get). + /// [`lookup`](RHash::lookup), [`lookup2`](RHash::lookup2), and + /// [`get`](RHash::get). /// /// # Examples /// /// ``` - /// use magnus::{eval, RHash}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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()); /// - /// let hash: RHash = eval( - /// r#" - /// hash = {"answer" => 42} - /// hash.default = 0 - /// hash - /// "#, - /// ) - /// .unwrap(); - /// assert_eq!(hash.fetch::<_, i64>("answer").unwrap(), 42); - /// assert!(hash.fetch::<_, i64>("missing").is_err()); - /// assert_eq!(hash.fetch::<_, Option>("answer").unwrap(), Some(42)); - /// assert!(hash.fetch::<_, Option>("missing").is_err()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn fetch(self, key: T) -> Result where @@ -474,12 +661,16 @@ impl RHash { /// # Examples /// /// ``` - /// use magnus::{eval, value::Qnil, RHash}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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()); /// - /// let hash: RHash = eval(r#"hash = {"answer" => 42}"#).unwrap(); - /// assert_eq!(hash.delete::<_, i64>("answer").unwrap(), 42); - /// assert!(hash.delete::<_, Qnil>("answer").is_ok()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn delete(self, key: T) -> Result where @@ -498,13 +689,17 @@ impl RHash { /// # Examples /// /// ``` - /// use magnus::{eval, RHash}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, RHash, Ruby}; /// - /// let hash: RHash = eval(r#"{"answer" => 42}"#).unwrap(); - /// assert!(!hash.is_empty()); - /// hash.clear().unwrap(); - /// assert!(hash.is_empty()); + /// 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())) })?; @@ -523,21 +718,24 @@ impl RHash { /// # Examples /// /// ``` - /// use magnus::{eval, r_hash::ForEach, RHash}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{r_hash::ForEach, Error, RHash, Ruby}; /// - /// let hash: RHash = eval(r#"{"foo" => 1, "bar" => 2, "baz" => 4, "qux" => 8}"#).unwrap(); - /// let mut found = None; - /// hash.foreach(|key: String, value: i64| { - /// if value > 3 { - /// found = Some(key); - /// Ok(ForEach::Stop) - /// } else { - /// Ok(ForEach::Continue) - /// } - /// }) - /// .unwrap(); - /// assert_eq!(found, Some(String::from("baz"))); + /// 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 @@ -559,8 +757,6 @@ impl RHash { let arg = &mut func as *mut F as VALUE; protect(|| { let fptr = iter:: as unsafe extern "C" fn(VALUE, VALUE, VALUE) -> c_int; - #[cfg(ruby_lt_2_7)] - let fptr: unsafe extern "C" fn() -> c_int = std::mem::transmute(fptr); rb_hash_foreach(self.as_rb_value(), Some(fptr), arg); Ruby::get_with(self).qnil() })?; @@ -583,13 +779,17 @@ impl RHash { /// ``` /// use std::collections::HashMap; /// - /// use magnus::{eval, RHash}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, RHash, Ruby}; /// - /// let r_hash: RHash = eval(r#"{"answer" => 42}"#).unwrap(); - /// let mut hash_map = HashMap::new(); - /// hash_map.insert(String::from("answer"), 42); - /// assert_eq!(r_hash.to_hash_map().unwrap(), hash_map); + /// 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 @@ -617,11 +817,15 @@ impl RHash { /// # Examples /// /// ``` - /// use magnus::{eval, RHash}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, RHash, Ruby}; /// - /// let r_hash: RHash = eval(r#"{"answer" => 42}"#).unwrap(); - /// assert_eq!(r_hash.to_vec().unwrap(), vec![(String::from("answer"), 42)]); + /// 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 @@ -641,11 +845,15 @@ impl RHash { /// # Examples /// /// ``` - /// use magnus::{eval, RHash}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, RHash, Ruby}; /// - /// let hash: RHash = eval(r#"{"foo" => 1, "bar" => 2, "baz" => 4}"#).unwrap(); - /// assert_eq!(hash.size().to_i64(), 3); + /// 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())) } @@ -656,11 +864,15 @@ impl RHash { /// # Examples /// /// ``` - /// use magnus::{eval, RHash}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// let hash: RHash = eval(r#"{"foo" => 1, "bar" => 2, "baz" => 4}"#).unwrap(); - /// 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 } @@ -671,13 +883,17 @@ impl RHash { /// # Examples /// /// ``` - /// use magnus::RHash; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let hash = RHash::new(); - /// assert!(hash.is_empty()); - /// hash.aset("answer", 42).unwrap(); - /// assert!(!hash.is_empty()); + /// 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 @@ -717,7 +933,14 @@ where } } -#[cfg(feature = "friendly-api")] +unsafe impl IntoValueFromNative for HashMap +where + K: IntoValueFromNative, + V: IntoValueFromNative, +{ +} + +#[cfg(feature = "old-api")] impl FromIterator<(K, V)> for RHash where K: IntoValue, @@ -727,11 +950,7 @@ where where I: IntoIterator, { - let hash = RHash::new(); - for (k, v) in iter { - let _ = hash.aset(k, v); - } - hash + get_ruby!().hash_from_iter(iter) } } diff --git a/src/r_match.rs b/src/r_match.rs index f962b4ab..c64cc5d2 100644 --- a/src/r_match.rs +++ b/src/r_match.rs @@ -65,22 +65,26 @@ impl RMatch { /// # Examples /// /// ``` - /// use magnus::{backref_get, RRegexp}; - /// # let _cleanup = unsafe { magnus::embed::init() }; - /// - /// let regexp = RRegexp::new(".([a-z])([a-z]*)([0-9])?", Default::default()).unwrap(); - /// regexp.reg_match("ex").unwrap(); - /// let match_data = 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); + /// 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())) }; @@ -94,37 +98,41 @@ impl RMatch { /// # Examples /// /// ``` - /// use magnus::{backref_get, RRegexp}; - /// # let _cleanup = unsafe { magnus::embed::init() }; - /// - /// let regexp = RRegexp::new(".([a-z])([a-z]*)([0-9])?", Default::default()).unwrap(); - /// regexp.reg_match("ex").unwrap(); - /// let match_data = 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 - /// ); + /// 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())) }; @@ -138,14 +146,18 @@ impl RMatch { /// # Examples /// /// ``` - /// use magnus::{backref_get, RRegexp}; - /// # let _cleanup = unsafe { magnus::embed::init() }; - /// - /// let regexp = RRegexp::new("Hello, (?.*)!", Default::default()).unwrap(); - /// regexp.reg_match("Hello, World!").unwrap(); - /// let match_data = backref_get().unwrap(); - /// assert_eq!(match_data.backref_number("subject").unwrap(), 1); - /// assert!(match_data.backref_number("foo").is_err()); + /// 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 @@ -166,14 +178,18 @@ impl RMatch { /// # Examples /// /// ``` - /// use magnus::{backref_get, RRegexp}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let regexp = RRegexp::new("b(.)r", Default::default()).unwrap(); - /// regexp.reg_match("foo bar baz").unwrap(); + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let regexp = ruby.reg_new("b(.)r", Default::default())?; + /// regexp.reg_match("foo bar baz")?; /// - /// let match_data = backref_get().unwrap(); - /// assert_eq!(match_data.matched().to_string().unwrap(), "bar"); + /// 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())) } @@ -184,14 +200,18 @@ impl RMatch { /// # Examples /// /// ``` - /// use magnus::{backref_get, RRegexp}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let regexp = RRegexp::new("b(.)r", Default::default()).unwrap(); - /// regexp.reg_match("foo bar baz").unwrap(); + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let regexp = ruby.reg_new("b(.)r", Default::default())?; + /// regexp.reg_match("foo bar baz")?; /// - /// let match_data = backref_get().unwrap(); - /// assert_eq!(match_data.pre().to_string().unwrap(), "foo "); + /// 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())) } @@ -202,14 +222,18 @@ impl RMatch { /// # Examples /// /// ``` - /// use magnus::{backref_get, RRegexp}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let regexp = RRegexp::new("b(.)r", Default::default()).unwrap(); - /// regexp.reg_match("foo bar baz").unwrap(); + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let regexp = ruby.reg_new("b(.)r", Default::default())?; + /// regexp.reg_match("foo bar baz")?; /// - /// let match_data = backref_get().unwrap(); - /// assert_eq!(match_data.post().to_string().unwrap(), " 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())) } @@ -222,14 +246,18 @@ impl RMatch { /// # Examples /// /// ``` - /// use magnus::{backref_get, RRegexp}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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 regexp = RRegexp::new("(.)oo b(.)r ba(.)", Default::default()).unwrap(); - /// regexp.reg_match("foo bar baz").unwrap(); + /// let match_data = ruby.backref_get().unwrap(); + /// assert_eq!(match_data.last().unwrap().to_string()?, "z"); /// - /// let match_data = backref_get().unwrap(); - /// assert_eq!(match_data.last().unwrap().to_string().unwrap(), "z"); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn last(self) -> Option { let value = unsafe { Value::new(rb_reg_match_last(self.as_rb_value())) }; diff --git a/src/r_rational.rs b/src/r_rational.rs index c1408992..352d84c4 100644 --- a/src/r_rational.rs +++ b/src/r_rational.rs @@ -94,6 +94,7 @@ impl RRational { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use std::num::NonZeroI64; /// /// use magnus::RRational; @@ -103,9 +104,10 @@ impl RRational { /// assert_eq!(rational.to_string(), "1/2"); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -118,11 +120,15 @@ impl RRational { /// ``` /// use std::num::NonZeroI64; /// - /// use magnus::RRational; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// let rational = RRational::new(6, NonZeroI64::new(9).unwrap()); - /// assert_eq!(rational.num().to_i64().unwrap(), 2); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn num(self) -> Integer { unsafe { Integer::from_rb_value_unchecked(rb_rational_num(self.as_rb_value())) } @@ -135,11 +141,15 @@ impl RRational { /// ``` /// use std::num::NonZeroI64; /// - /// use magnus::RRational; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// let rational = RRational::new(6, NonZeroI64::new(9).unwrap()); - /// assert_eq!(rational.den().to_i64().unwrap(), 3); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn den(self) -> Integer { unsafe { Integer::from_rb_value_unchecked(rb_rational_den(self.as_rb_value())) } diff --git a/src/r_regexp.rs b/src/r_regexp.rs index 82a7fcff..f05aa3e9 100644 --- a/src/r_regexp.rs +++ b/src/r_regexp.rs @@ -103,6 +103,7 @@ impl RRegexp { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{r_regexp::Opts, rb_assert, RRegexp}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -110,9 +111,10 @@ impl RRegexp { /// rb_assert!(r#"regexp == /foo/i"#, regexp); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -123,11 +125,15 @@ impl RRegexp { /// # Examples /// /// ``` - /// use magnus::{r_regexp::Opts, rb_assert, RRegexp, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{r_regexp::Opts, rb_assert, Error, RRegexp, Ruby}; /// - /// let regexp = RRegexp::new_str(RString::new("foo"), Opts::new().ignorecase()).unwrap(); - /// rb_assert!(r#"regexp == /foo/i"#, regexp); + /// 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 { @@ -140,12 +146,16 @@ impl RRegexp { /// # Examples /// /// ``` - /// use magnus::RRegexp; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// let regexp = RRegexp::new("x", Default::default()).unwrap(); - /// assert_eq!(regexp.reg_match("text").unwrap(), Some(2)); - /// assert_eq!(regexp.reg_match("test").unwrap(), None); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn reg_match(self, s: T) -> Result, Error> where @@ -161,16 +171,20 @@ impl RRegexp { /// # Examples /// /// ``` - /// use magnus::{eval, RRegexp}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, RRegexp, Ruby}; /// - /// let regexp: RRegexp = eval("/x/i").unwrap(); - /// assert!(regexp.options().is_ignorecase()); - /// assert!(!regexp.options().is_multiline()); + /// 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()); /// - /// let regexp: RRegexp = 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) } diff --git a/src/r_string.rs b/src/r_string.rs index a767dc27..51312cf1 100644 --- a/src/r_string.rs +++ b/src/r_string.rs @@ -9,23 +9,18 @@ use std::{ mem::transmute, os::raw::{c_char, c_long}, path::{Path, PathBuf}, - ptr::{self, NonNull}, - slice, str, + ptr, slice, str, }; #[cfg(ruby_gte_3_0)] use rb_sys::rb_str_to_interned_str; -#[cfg(all(ruby_gte_3_0, ruby_lt_3_2))] -use rb_sys::ruby_rstring_consts::RSTRING_EMBED_LEN_SHIFT; -#[cfg(ruby_lt_3_0)] -use rb_sys::ruby_rstring_flags::RSTRING_EMBED_LEN_SHIFT; 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, VALUE, + ruby_rstring_flags, ruby_value_type, RSTRING_LEN, RSTRING_PTR, VALUE, }; use crate::{ @@ -315,11 +310,6 @@ impl RString { 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 _) } - } - /// Create a new Ruby string from the Rust string `s`. /// /// The encoding of the Ruby string will be UTF-8. @@ -332,6 +322,7 @@ impl RString { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{rb_assert, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -339,9 +330,10 @@ impl RString { /// rb_assert!(r#"val == "example""#, val); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -367,6 +359,7 @@ impl RString { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{rb_assert, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -375,9 +368,10 @@ impl RString { /// rb_assert!(r#"buf == "\r\x0E\n\r\v\x0E\x0E\x0F""#, buf); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -396,6 +390,7 @@ impl RString { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{rb_assert, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -406,9 +401,10 @@ impl RString { /// rb_assert!(r#"s == "foobarbaz""#, s); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -426,6 +422,7 @@ impl RString { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{rb_assert, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -433,9 +430,10 @@ impl RString { /// rb_assert!(r#"buf == "\r\x0E\n\r\v\x0E\x0E\x0F""#, buf); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -451,6 +449,7 @@ impl RString { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{encoding::RbEncoding, rb_assert, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -459,6 +458,7 @@ impl RString { /// ``` /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{encoding::RbEncoding, rb_assert, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -466,9 +466,10 @@ impl RString { /// rb_assert!(r#"val == "\xFF\x80\x80".force_encoding("BINARY")"#, val); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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 @@ -490,6 +491,7 @@ impl RString { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{rb_assert, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -498,6 +500,7 @@ impl RString { /// ``` /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{rb_assert, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -505,9 +508,10 @@ impl RString { /// rb_assert!(r#"c == "🦀""#, c); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -526,6 +530,7 @@ impl RString { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{encoding::RbEncoding, rb_assert, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -534,6 +539,7 @@ impl RString { /// ``` /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{encoding::RbEncoding, rb_assert, RString}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -541,9 +547,10 @@ impl RString { /// rb_assert!(r#"c == "🦀""#, c); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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 @@ -562,15 +569,19 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::{rb_assert, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// let s = RString::new("example"); - /// let dup = RString::new_shared(s); - /// rb_assert!("s == dup", s, dup); - /// // mutating one doesn't mutate both - /// dup.cat("foo"); - /// rb_assert!("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())) } @@ -584,15 +595,19 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::{rb_assert, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{rb_assert, Error, RString, Ruby}; /// - /// let orig = RString::new("example"); - /// let frozen = RString::new_frozen(orig); - /// rb_assert!(r#"frozen == "example""#, frozen); - /// // mutating original doesn't impact the frozen copy - /// orig.cat("foo"); - /// rb_assert!(r#"frozen == "example""#, frozen); + /// 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())) } @@ -612,38 +627,28 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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]) }; /// - /// let s = RString::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] { - #[cfg(ruby_gte_3_1)] - unsafe fn embedded_ary_ptr(rstring: RString) -> *const u8 { - &rstring.as_internal().as_ref().as_.embed.ary as *const _ as *const u8 - } - - #[cfg(ruby_lt_3_1)] - unsafe fn embedded_ary_ptr(rstring: RString) -> *const u8 { - &rstring.as_internal().as_ref().as_.ary as *const _ as *const u8 - } - debug_assert_value!(self); - let r_basic = self.r_basic_unchecked(); - let f = r_basic.as_ref().flags; - if (f & ruby_rstring_flags::RSTRING_NOEMBED as VALUE) != 0 { - let h = self.as_internal().as_ref().as_.heap; - slice::from_raw_parts(h.ptr as *const u8, self.len()) - } else { - slice::from_raw_parts(embedded_ary_ptr(self), self.len()) - } + 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. @@ -657,19 +662,23 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::{Error, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, RString, Ruby}; /// - /// let s = RString::new("🦀 café"); + /// 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>>().unwrap() - /// }; + /// 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]); /// - /// assert_eq!(codepoints, [129408, 32, 99, 97, 102, 233]); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub unsafe fn codepoints(&self) -> Codepoints { Codepoints { @@ -689,27 +698,31 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, RString, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let s = ruby.str_new("🦀 café"); /// - /// let s = RString::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] - /// ] - /// ); + /// // 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 { @@ -723,12 +736,16 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let s = RString::new("🌊🦀🏝️"); - /// assert_eq!(s.offset(1), 4); - /// assert_eq!(s.offset(2), 8); + /// 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 } @@ -743,19 +760,27 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::{eval, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{eval, Error, RString, Ruby}; /// - /// let s: RString = eval!(r#""café""#).unwrap(); - /// assert!(s.is_utf8_compatible_encoding()); + /// 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, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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()); /// - /// let s: RString = eval!(r#""café".encode("ISO-8859-1")"#).unwrap(); - /// 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); @@ -770,15 +795,19 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::{encoding::RbEncoding, eval, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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]) }; /// - /// let s: RString = eval!(r#""café".encode("ISO-8859-1")"#).unwrap(); - /// // 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(RbEncoding::utf8()).unwrap(); - /// 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 @@ -806,14 +835,22 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::{encoding::RbEncoding, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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"); /// - /// // 156 is invalid for utf-8 - /// let s = RString::enc_new([156, 57, 57], RbEncoding::utf8()); - /// assert_eq!(s.scrub(None).unwrap().unwrap().to_string().unwrap(), "�99"); - /// assert_eq!(s.scrub(Some(RString::new("?"))).unwrap().unwrap().to_string().unwrap(), "?99"); - /// assert_eq!(s.scrub(Some(RString::new(""))).unwrap().unwrap().to_string().unwrap(), "99"); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() + /// ``` pub fn scrub(self, replacement: Option) -> Result, Error> { let val = protect(|| unsafe { Value::new(rb_str_scrub( @@ -838,34 +875,38 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::{encoding::Coderange, prelude::*, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{encoding::Coderange, prelude::*, Error, Ruby}; /// - /// // Coderange is unknown on creation. - /// let s = RString::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", ()).unwrap(); - /// assert_eq!(s.enc_coderange(), Coderange::SevenBit); - /// - /// // Operations with two strings with known coderanges will set it - /// // appropriately. - /// let t = RString::new("🦀"); - /// let _: usize = t.funcall("length", ()).unwrap(); - /// assert_eq!(t.enc_coderange(), Coderange::Valid); - /// s.buf_append(t).unwrap(); - /// 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); + /// 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); /// - /// // Which may leave the string with a broken encoding. - /// let _: usize = s.funcall("length", ()).unwrap(); - /// assert_eq!(s.enc_coderange(), Coderange::Broken); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn enc_coderange(self) -> Coderange { unsafe { @@ -884,11 +925,15 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::{encoding::Coderange, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{encoding::Coderange, Error, Ruby}; /// - /// let s = RString::new("test"); - /// assert_eq!(s.enc_coderange_scan(), Coderange::SevenBit); + /// 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) } @@ -899,16 +944,20 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::{encoding::Coderange, prelude::*, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// let s = RString::new("🦀"); - /// // trigger setting coderange - /// let _: usize = s.funcall("length", ()).unwrap(); - /// assert_eq!(s.enc_coderange(), Coderange::Valid); + /// s.enc_coderange_clear(); + /// assert_eq!(s.enc_coderange(), Coderange::Unknown); /// - /// s.enc_coderange_clear(); - /// assert_eq!(s.enc_coderange(), Coderange::Unknown); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn enc_coderange_clear(self) { unsafe { @@ -933,17 +982,14 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::{ - /// encoding::{self, Coderange}, - /// exception, - /// prelude::*, - /// Error, RString, - /// }; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{encoding::Coderange, prelude::*, Error, RString, Ruby}; /// - /// fn crabbify(s: RString) -> Result<(), Error> { - /// if s.enc_get() != encoding::Index::utf8() { - /// return Err(Error::new(exception::encoding_error(), "expected utf-8")); + /// 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 @@ -959,12 +1005,17 @@ impl RString { /// Ok(()) /// } /// - /// let s = RString::new("test"); - /// // trigger setting coderange - /// let _: usize = s.funcall("length", ()).unwrap(); + /// 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); /// - /// crabbify(s).unwrap(); - /// 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(); @@ -988,13 +1039,17 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let s = RString::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") }; + /// 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() @@ -1017,13 +1072,17 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let s = RString::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().unwrap(), "example") }; + /// 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() @@ -1070,13 +1129,17 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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") }; /// - /// let s = RString::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> { @@ -1090,11 +1153,15 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let s = RString::new("example"); - /// assert_eq!(s.to_string().unwrap(), "example"); + /// 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); @@ -1114,11 +1181,15 @@ impl RString { /// /// ``` /// use bytes::Bytes; - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let s = RString::new("example"); - /// assert_eq!(s.to_bytes(), Bytes::from("example")); + /// 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")] @@ -1133,11 +1204,15 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let s = ruby.str_new("a"); + /// assert_eq!(s.to_char()?, 'a'); /// - /// let s = RString::new("a"); - /// assert_eq!(s.to_char().unwrap(), 'a'); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn to_char(self) -> Result { let handle = Ruby::get_with(self); @@ -1168,14 +1243,15 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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""#); /// - /// let s = RString::new("🦀 café"); - /// assert_eq!( - /// s.dump().unwrap().to_string().unwrap(), - /// 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())) }) @@ -1192,26 +1268,34 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::{eval, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{eval, Error, RString, Ruby}; /// - /// let s: RString = eval!( - /// r#" - /// ## frozen_string_literal: true + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let s: RString = eval!( + /// ruby, + /// r#" + /// ## frozen_string_literal: true /// - /// "example" - /// "# - /// ) - /// .unwrap(); - /// assert!(s.is_interned()); + /// "example" + /// "# + /// )?; + /// assert!(s.is_interned()); + /// + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` /// /// ``` - /// use magnus::{eval, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{eval, Error, RString, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let s: RString = eval!(ruby, r#""example""#)?; + /// assert!(!s.is_interned()); /// - /// let s: RString = eval!(r#""example""#).unwrap(); - /// assert!(!s.is_interned()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn is_interned(self) -> bool { unsafe { @@ -1230,26 +1314,34 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::{eval, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{eval, Error, RString, Ruby}; /// - /// let s: RString = eval!( - /// r#" - /// ## frozen_string_literal: true + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let s: RString = eval!( + /// ruby, + /// r#" + /// ## frozen_string_literal: true /// - /// "example" - /// "# - /// ) - /// .unwrap(); - /// assert!(s.as_interned_str().is_some()); + /// "example" + /// "# + /// )?; + /// assert!(s.as_interned_str().is_some()); + /// + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` /// /// ``` - /// use magnus::{eval, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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()); /// - /// let s: RString = eval!(r#""example""#).unwrap(); - /// 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)) @@ -1261,11 +1353,15 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let fstring = RString::new("example").to_interned_str(); - /// assert_eq!(fstring.as_str().unwrap(), "example"); + /// 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)))] @@ -1283,13 +1379,17 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let a = RString::new("foo"); - /// let b = RString::new("bar"); - /// a.buf_append(b).unwrap(); - /// assert_eq!(a.to_string().unwrap(), "foobar"); + /// 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 { @@ -1308,13 +1408,17 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let buf = RString::buf_new(4096); - /// buf.cat(&[102, 111, 111]); - /// buf.cat("bar"); - /// assert_eq!(buf.to_string().unwrap(), "foobar"); + /// 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(); @@ -1330,13 +1434,17 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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"); /// - /// let a = RString::new("foo"); - /// let b = RString::new("bar"); - /// a.replace(b).unwrap(); - /// assert_eq!(a.to_string().unwrap(), "bar"); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn replace(self, other: Self) -> Result<(), Error> { protect(|| { @@ -1357,16 +1465,20 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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"); /// - /// let a = RString::new("foo"); - /// let b = RString::new("bar"); - /// a.shared_replace(b).unwrap(); - /// assert_eq!(a.to_string().unwrap(), "bar"); - /// // mutating one doesn't mutate both - /// b.cat("foo"); - /// assert_eq!(a.to_string().unwrap(), "bar"); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn shared_replace(self, other: Self) -> Result<(), Error> { protect(|| { @@ -1382,25 +1494,29 @@ impl RString { /// 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 accomdate `other`. + /// contracted as needed to accommodate `other`. /// /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let s = RString::new("foo"); - /// s.update(-1, 1, RString::new("x")).unwrap(); - /// assert_eq!(s.to_string().unwrap(), "fox"); + /// 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 = RString::new("splat"); - /// s.update(0, 3, RString::new("b")).unwrap(); - /// assert_eq!(s.to_string().unwrap(), "bat"); + /// let s = ruby.str_new("corncob"); + /// s.update(1, 5, ruby.str_new("ra"))?; + /// assert_eq!(s.to_string()?, "crab"); /// - /// let s = RString::new("corncob"); - /// s.update(1, 5, RString::new("ra")).unwrap(); - /// assert_eq!(s.to_string().unwrap(), "crab"); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn update(self, beg: isize, len: usize, other: Self) -> Result<(), Error> { protect(|| { @@ -1422,14 +1538,18 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let a = RString::new("foo"); - /// let b = RString::new("bar"); - /// assert_eq!(a.plus(b).unwrap().to_string().unwrap(), "foobar"); - /// assert_eq!(a.to_string().unwrap(), "foo"); - /// assert_eq!(b.to_string().unwrap(), "bar"); + /// 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 { @@ -1442,13 +1562,14 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// assert_eq!(ruby.str_new("foo").times(3).to_string()?, "foofoofoo"); /// - /// assert_eq!( - /// RString::new("foo").times(3).to_string().unwrap(), - /// "foofoofoo" - /// ); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn times(self, num: usize) -> Self { let num = Ruby::get_with(self).into_value(num); @@ -1462,12 +1583,16 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let s = RString::new("foobar"); - /// s.drop_bytes(3).unwrap(); - /// assert_eq!(s.to_string().unwrap(), "bar"); + /// 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(|| { @@ -1484,44 +1609,19 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let s = RString::new("🦀 Hello, Ferris"); - /// assert_eq!(s.len(), 18); - /// ``` - #[cfg(ruby_gte_3_3)] - pub fn len(self) -> usize { - debug_assert_value!(self); - unsafe { self.as_internal().as_ref().len as usize } - } - - /// Returns the number of bytes in `self`. - /// - /// See also [`length`](RString::length). - /// - /// # Examples - /// - /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let s = ruby.str_new("🦀 Hello, Ferris"); + /// assert_eq!(s.len(), 18); /// - /// let s = RString::new("🦀 Hello, Ferris"); - /// assert_eq!(s.len(), 18); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` - #[cfg(ruby_lte_3_2)] pub fn len(self) -> usize { debug_assert_value!(self); - unsafe { - let r_basic = self.r_basic_unchecked(); - let f = r_basic.as_ref().flags; - if (f & ruby_rstring_flags::RSTRING_NOEMBED as VALUE) != 0 { - let h = self.as_internal().as_ref().as_.heap; - h.len as usize - } else { - embed_len(self, f) as usize - } - } + unsafe { RSTRING_LEN(self.as_rb_value()) as usize } } /// Returns the number of characters in `self`. @@ -1531,11 +1631,15 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let s = ruby.str_new("🦀 Hello, Ferris"); + /// assert_eq!(s.length(), 15); /// - /// let s = RString::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 } @@ -1546,13 +1650,17 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let s = RString::with_capacity(9); - /// s.cat("foo"); - /// assert_eq!(3, s.len()); - /// assert!(s.capacity() >= 9); + /// 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 } @@ -1563,11 +1671,15 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let s = ruby.str_new(""); + /// assert!(s.is_empty()); /// - /// let s = RString::new(""); - /// assert!(s.is_empty()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn is_empty(self) -> bool { self.len() == 0 @@ -1580,12 +1692,16 @@ impl RString { /// ``` /// use std::cmp::Ordering; /// - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let a = RString::new("a"); - /// let b = RString::new("b"); - /// assert_eq!(Ordering::Less, a.cmp(b)); + /// 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 @@ -1619,11 +1735,15 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::RString; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let s = RString::new("foobarbaz"); - /// assert_eq!(s.ellipsize(6).to_string().unwrap(), "foo..."); + /// 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 { @@ -1641,22 +1761,26 @@ impl RString { /// # Examples /// /// ``` - /// use magnus::{prelude::*, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, Ruby}; /// - /// let s = RString::new(" foo bar baz "); - /// assert_eq!( - /// Vec::::try_convert(s.split("").as_value()).unwrap(), - /// vec![" ", "f", "o", "o", " ", " ", "b", "a", "r", " ", " ", "b", "a", "z", " "] - /// ); - /// assert_eq!( - /// Vec::::try_convert(s.split(" ").as_value()).unwrap(), - /// vec!["foo", "bar", "baz"] - /// ); - /// assert_eq!( - /// Vec::::try_convert(s.split(" bar ").as_value()).unwrap(), - /// vec![" foo ", " baz "] - /// ); + /// 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(); @@ -1664,18 +1788,6 @@ impl RString { } } -#[cfg(ruby_3_2)] -unsafe fn embed_len(value: RString, _: VALUE) -> c_long { - value.as_internal().as_ref().as_.embed.len -} - -#[cfg(ruby_lt_3_2)] -unsafe fn embed_len(_: RString, mut f: VALUE) -> VALUE { - f &= ruby_rstring_flags::RSTRING_EMBED_LEN_MASK as VALUE; - f >>= RSTRING_EMBED_LEN_SHIFT as VALUE; - f -} - impl fmt::Display for RString { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) @@ -1711,9 +1823,10 @@ pub trait IntoRString: Sized { /// Panics if called from a non-Ruby thread. See /// [`IntoRString::into_r_string_with`] for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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!()) @@ -1808,7 +1921,7 @@ unsafe impl IntoValueFromNative for &str {} impl IntoValue for bytes::Bytes { #[inline] fn into_value_with(self, handle: &Ruby) -> Value { - handle.str_from_slice(self.as_ref()).into() + handle.str_from_slice(self.as_ref()).into_value_with(handle) } } @@ -1889,19 +2002,23 @@ impl FString { /// # Examples /// /// ``` - /// use magnus::{eval, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{eval, Error, RString, Ruby}; /// - /// let s: RString = eval!( - /// r#" - /// ## frozen_string_literal: true + /// 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]); /// - /// "example" - /// "# - /// ) - /// .unwrap(); - /// 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() } @@ -1913,19 +2030,23 @@ impl FString { /// # Examples /// /// ``` - /// use magnus::{eval, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{eval, Error, RString, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let s: RString = eval!( + /// ruby, + /// r#" + /// ## frozen_string_literal: true /// - /// let s: RString = eval!( - /// r#" - /// ## frozen_string_literal: true + /// "example" + /// "# + /// )?; + /// let fstring = s.as_interned_str().unwrap(); + /// assert_eq!(fstring.test_as_str().unwrap(), "example"); /// - /// "example" - /// "# - /// ) - /// .unwrap(); - /// 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() } @@ -1937,19 +2058,23 @@ impl FString { /// # Examples /// /// ``` - /// use magnus::{eval, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{eval, Error, RString, Ruby}; /// - /// let s: RString = eval!( - /// r#" - /// ## frozen_string_literal: true + /// 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"); /// - /// "example" - /// "# - /// ) - /// .unwrap(); - /// let fstring = s.as_interned_str().unwrap(); - /// assert_eq!(fstring.as_str().unwrap(), "example"); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn as_str(self) -> Result<&'static str, Error> { unsafe { self.as_r_string().as_str_unconstrained() } @@ -1962,19 +2087,23 @@ impl FString { /// # Examples /// /// ``` - /// use magnus::{eval, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{eval, Error, RString, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let s: RString = eval!( + /// ruby, + /// r#" + /// ## frozen_string_literal: true /// - /// let s: RString = eval!( - /// r#" - /// ## frozen_string_literal: true + /// "example" + /// "# + /// )?; + /// let fstring = s.as_interned_str().unwrap(); + /// assert_eq!(fstring.to_string_lossy(), "example"); /// - /// "example" - /// "# - /// ) - /// .unwrap(); - /// 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()) @@ -2059,11 +2188,15 @@ impl<'a> Iterator for CharBytes<'a> { /// # Examples /// /// ``` -/// use magnus::{r_string, rb_assert}; -/// # let _cleanup = unsafe { magnus::embed::init() }; +/// 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); /// -/// let s = r_string!("Hello, world!"); -/// rb_assert!(r#"s == "Hello, world!""#, s); +/// Ok(()) +/// } +/// # Ruby::init(example).unwrap() /// ``` #[macro_export] macro_rules! r_string { diff --git a/src/r_struct.rs b/src/r_struct.rs index 8fbedba4..97c6f521 100644 --- a/src/r_struct.rs +++ b/src/r_struct.rs @@ -11,6 +11,8 @@ use std::{ 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, @@ -40,12 +42,8 @@ mod sys { use rb_sys::ruby_fl_ushift::RUBY_FL_USHIFT; use rb_sys::{ruby_fl_type, RBasic, VALUE}; - #[cfg(ruby_gte_2_7)] pub const EMBED_LEN_MAX: u32 = rb_sys::ruby_rvalue_flags::RVALUE_EMBED_LEN_MAX as u32; - #[cfg(ruby_lt_2_7)] - pub const EMBED_LEN_MAX: u32 = 3; - 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; @@ -85,14 +83,18 @@ impl RStruct { /// # Examples /// /// ``` - /// use magnus::{define_global_const, eval, r_struct::define_struct, RStruct}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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)?; /// - /// let struct_class = define_struct(None, ("foo", "bar")).unwrap(); - /// define_global_const("Example", struct_class).unwrap(); + /// assert!(RStruct::from_value(ruby.eval(r#"Example.new(1, 2)"#)?).is_some()); + /// assert!(RStruct::from_value(ruby.eval(r#"Object.new"#)?).is_none()); /// - /// assert!(RStruct::from_value(eval(r#"Example.new(1, 2)"#).unwrap()).is_some()); - /// assert!(RStruct::from_value(eval(r#"Object.new"#).unwrap()).is_none()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn from_value(val: Value) -> Option { @@ -119,8 +121,7 @@ impl RStruct { /// /// Ruby may modify or free the memory backing the returned slice, the /// caller must ensure this does not happen. - #[deprecated(since = "0.6.0")] - pub unsafe fn as_slice(&self) -> &[Value] { + unsafe fn as_slice(&self) -> &[Value] { self.as_slice_unconstrained() } @@ -146,15 +147,18 @@ impl RStruct { /// # Examples /// /// ``` - /// use magnus::{prelude::*, r_struct::define_struct, RStruct}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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); /// - /// let struct_class = define_struct(None, ("foo", "bar")).unwrap(); - /// let instance = RStruct::from_value(struct_class.new_instance((1, 2)).unwrap()).unwrap(); - /// assert_eq!(instance.get::(0).unwrap(), 1); - /// assert_eq!(instance.get::(1).unwrap(), 2); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` - #[allow(deprecated)] pub fn get(self, index: usize) -> Result where T: TryConvert, @@ -185,14 +189,18 @@ impl RStruct { /// # Examples /// /// ``` - /// use magnus::{prelude::*, r_struct::define_struct, RStruct, Symbol}; - /// # let _cleanup = unsafe { magnus::embed::init() }; - /// - /// let struct_class = define_struct(None, ("foo", "bar", "baz")).unwrap(); - /// let instance = RStruct::from_value(struct_class.new_instance((1, 2, 3)).unwrap()).unwrap(); - /// assert_eq!(instance.aref::<_, i64>(0).unwrap(), 1); - /// assert_eq!(instance.aref::<_, i64>("bar").unwrap(), 2); - /// assert_eq!(instance.aref::<_, i64>(Symbol::new("baz")).unwrap(), 3); + /// 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 @@ -211,20 +219,24 @@ impl RStruct { /// # Examples /// /// ``` - /// use magnus::{prelude::*, r_struct::define_struct, rb_assert, RStruct, Symbol}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, rb_assert, Error, RStruct, Ruby}; /// - /// let struct_class = define_struct(None, ("foo", "bar", "baz")).unwrap(); - /// let instance = RStruct::from_value(struct_class.new_instance((1, 2, 3)).unwrap()).unwrap(); + /// 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).unwrap(); - /// rb_assert!("instance.foo == 4", instance); + /// instance.aset(0, 4)?; + /// rb_assert!("instance.foo == 4", instance); /// - /// instance.aset("bar", 5).unwrap(); - /// rb_assert!("instance.bar == 5", instance); + /// instance.aset("bar", 5)?; + /// rb_assert!("instance.bar == 5", instance); /// - /// instance.aset(Symbol::new("baz"), 6).unwrap(); - /// rb_assert!("instance.baz == 6", 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 @@ -251,13 +263,17 @@ impl RStruct { /// # Examples /// /// ``` - /// use magnus::{prelude::*, r_struct::define_struct, RStruct}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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(); /// - /// let struct_class = define_struct(None, ("foo", "bar", "baz")).unwrap(); - /// let instance = RStruct::from_value(struct_class.new_instance(()).unwrap()).unwrap(); + /// assert_eq!(instance.size(), 3); /// - /// 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() } @@ -268,13 +284,17 @@ impl RStruct { /// # Examples /// /// ``` - /// use magnus::{prelude::*, r_struct::define_struct, RStruct}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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(); /// - /// let struct_class = define_struct(None, ("foo", "bar", "baz")).unwrap(); - /// let instance = RStruct::from_value(struct_class.new_instance(()).unwrap()).unwrap(); + /// assert_eq!(instance.members()?, &["foo", "bar", "baz"]); /// - /// assert_eq!(instance.members().unwrap(), &["foo", "bar", "baz"]); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn members(self) -> Result>, Error> { unsafe { @@ -292,13 +312,17 @@ impl RStruct { /// # Examples /// /// ``` - /// use magnus::{prelude::*, r_struct::define_struct, RStruct}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, RStruct, Ruby}; /// - /// let struct_class = define_struct(None, ("foo", "bar")).unwrap(); - /// let instance = RStruct::from_value(struct_class.new_instance((1, 2)).unwrap()).unwrap(); - /// assert_eq!(instance.getmember::<_, i64>("foo").unwrap(), 1); - /// assert_eq!(instance.getmember::<_, i64>("bar").unwrap(), 2); + /// 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 @@ -405,6 +429,38 @@ impl Ruby { { 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. @@ -422,6 +478,7 @@ impl Ruby { /// 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() }; /// @@ -438,6 +495,7 @@ impl Ruby { /// under `Struct`: /// /// ``` +/// # #![allow(deprecated)] /// use magnus::{prelude::*, r_struct::define_struct}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -449,9 +507,10 @@ impl Ruby { /// assert_eq!(instance.inspect(), "#") /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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 @@ -465,6 +524,9 @@ mod private { 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; @@ -484,6 +546,18 @@ macro_rules! impl_struct_members { )) }) } + + #[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::(), + )) + }) + } } }); } diff --git a/src/r_typed_data.rs b/src/r_typed_data.rs index 514ebcc2..b4c4a34e 100644 --- a/src/r_typed_data.rs +++ b/src/r_typed_data.rs @@ -1,6 +1,6 @@ use std::{fmt, ptr::NonNull}; -use rb_sys::{self, rb_check_typeddata, rb_data_typed_object_wrap, ruby_value_type}; +use rb_sys::{self, rb_check_typeddata, rb_data_typed_object_wrap, ruby_value_type, VALUE}; use crate::{ class::RClass, @@ -173,8 +173,7 @@ impl RTypedData { /// # Examples /// /// ``` - /// use magnus::{class, define_class, eval, function, prelude::*, RTypedData}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{function, prelude::*, Error, RTypedData, Ruby}; /// /// #[magnus::wrap(class = "Point")] /// struct Point { @@ -182,13 +181,16 @@ impl RTypedData { /// y: isize, /// } /// - /// let point_class = define_class("Point", class::object()).unwrap(); - /// point_class - /// .define_singleton_method("new", function!(|x, y| Point { x, y }, 2)) - /// .unwrap(); + /// 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()); /// - /// assert!(RTypedData::from_value(eval(r#"Point.new(1, 2)"#).unwrap()).is_some()); - /// assert!(RTypedData::from_value(eval(r#"Object.new"#).unwrap()).is_none()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap(); /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` #[inline] @@ -197,12 +199,18 @@ impl RTypedData { (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| { - (typed_data.as_ref().typed_flag == 1) + 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 @@ -213,6 +221,7 @@ impl RTypedData { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{class, define_class, prelude::*, RTypedData}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -229,9 +238,10 @@ impl RTypedData { /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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 @@ -255,6 +265,7 @@ impl RTypedData { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{class, define_class, prelude::*, RTypedData}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -280,6 +291,7 @@ impl RTypedData { /// `new` method rather than `initialize`) /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{ /// class, define_class, eval, function, method, prelude::*, RClass, RTypedData, Value, /// }; @@ -319,9 +331,10 @@ impl RTypedData { /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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 @@ -335,8 +348,7 @@ impl RTypedData { /// # Examples /// /// ``` - /// use magnus::{class, define_class, RTypedData}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// /// #[magnus::wrap(class = "Point")] /// #[derive(Debug, PartialEq, Eq)] @@ -345,10 +357,15 @@ impl RTypedData { /// y: isize, /// } /// - /// define_class("Point", class::object()).unwrap(); - /// let value = RTypedData::wrap(Point { x: 4, y: 2 }); + /// 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 }); /// - /// assert_eq!(value.get::().unwrap(), &Point { x: 4, y: 2 }); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn get(&self) -> Result<&T, Error> diff --git a/src/range.rs b/src/range.rs index 0553ef0d..90896c33 100644 --- a/src/range.rs +++ b/src/range.rs @@ -111,6 +111,7 @@ impl Range { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::rb_assert; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -119,6 +120,7 @@ impl Range { /// ``` /// /// ``` + /// # #![allow(deprecated)] /// use magnus::rb_assert; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -126,9 +128,10 @@ impl Range { /// rb_assert!("range == (2...7)", range); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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 @@ -216,9 +219,7 @@ impl Range { /// use magnus::eval; /// # let _cleanup = unsafe { magnus::embed::init() }; /// - /// # #[cfg(ruby_gte_2_7)] /// let range: magnus::Range = eval("..7").unwrap(); - /// # #[cfg(ruby_gte_2_7)] /// assert_eq!(range.beg_len(10).unwrap(), (0, 8)); /// ``` /// @@ -277,9 +278,7 @@ impl Range { /// use magnus::eval; /// # let _cleanup = unsafe { magnus::embed::init() }; /// - /// # #[cfg(ruby_gte_2_7)] /// let range: magnus::Range = eval("..7").unwrap(); - /// # #[cfg(ruby_gte_2_7)] /// assert_eq!(range.to_range_with_len(10).unwrap(), 0..8); /// ``` /// diff --git a/src/rb_sys.rs b/src/rb_sys.rs index 00fd2a0e..34f6b7b1 100644 --- a/src/rb_sys.rs +++ b/src/rb_sys.rs @@ -35,32 +35,43 @@ use crate::{ /// Converts from a [`Value`] to a raw [`VALUE`]. pub trait AsRawValue { - /// Convert [`magnus::Value`](Value) to [`rb_sys::VALUE`](VALUE). + /// Convert [`magnus::Value`](Value) to [`rb_sys::VALUE`]. + /// + /// # Examples /// /// ``` - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{ + /// rb_sys::{protect, AsRawValue}, + /// Error, Ruby, + /// }; /// - /// use magnus::{rb_sys::AsRawValue, RString}; + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let foo = ruby.str_new("foo"); + /// let bar = ruby.str_new("bar"); /// - /// let foo = RString::new("foo"); - /// let bar = RString::new("bar"); + /// protect(|| unsafe { rb_sys::rb_str_buf_append(foo.as_raw(), bar.as_raw()) })?; /// - /// unsafe { rb_sys::rb_str_buf_append(foo.as_raw(), bar.as_raw()) }; + /// assert_eq!(foo.to_string()?, "foobar"); /// - /// assert_eq!(foo.to_string().unwrap(), "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`](VALUE) to [`magnus::Value`](Value). + /// 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() }; /// @@ -90,29 +101,35 @@ impl FromRawValue for Value { /// Trait to convert a [`Id`] to a raw [`ID`]. pub trait AsRawId { - /// Convert [`magnus::value::Id`](Id) to [`rb_sys::ID`](ID). + /// Convert [`magnus::value::Id`](Id) to [`rb_sys::ID`]. + /// + /// # Examples /// /// ``` /// use magnus::{ /// prelude::*, /// rb_sys::{AsRawId, FromRawId}, /// value::Id, - /// Symbol, + /// Error, Ruby, Symbol, /// }; - /// # let _cleanup = unsafe { magnus::embed::init() }; /// - /// let foo: Id = Symbol::new("foo").into(); - /// let raw = foo.as_raw(); - /// let from_raw_val: Symbol = unsafe { Id::from_raw(raw) }.into(); + /// 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"); /// - /// 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`](ID) to [`magnus::value::Id`](ID). + /// Convert [`rb_sys::ID`] to [`magnus::value::Id`](Id). /// /// # Safety /// @@ -121,19 +138,25 @@ pub trait FromRawId { /// from this function will void all saftey guarantees provided by /// Magnus. /// + /// # Examples + /// /// ``` /// use magnus::{ /// prelude::*, /// rb_sys::{AsRawId, FromRawId}, /// value::Id, - /// Symbol, + /// Error, Ruby, Symbol, /// }; - /// # let _cleanup = unsafe { magnus::embed::init() }; /// - /// let foo: Id = Symbol::new("foo").into(); - /// let from_raw_val: Symbol = unsafe { Id::from_raw(foo.as_raw()) }.into(); + /// 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"); /// - /// assert_eq!(from_raw_val.inspect(), ":foo"); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` unsafe fn from_raw(id: ID) -> Self; } diff --git a/src/scan_args.rs b/src/scan_args.rs index b8b724db..61be91d6 100644 --- a/src/scan_args.rs +++ b/src/scan_args.rs @@ -448,12 +448,9 @@ impl ScanArgsBlock for T where T: private::ScanArgsBlock {} /// /// `TCPServer::new`'s argument handling. This is roughly equivalent to /// `def new(hostname=nil, port)`. +/// /// ``` -/// use magnus::{ -/// class, define_class, error::Error, function, prelude::*, scan_args::scan_args, Value, -/// }; -/// # use magnus::IntoValue; -/// # let _cleanup = unsafe { magnus::embed::init() }; +/// use magnus::{function, prelude::*, scan_args::scan_args, Error, Ruby, Value}; /// /// fn tcp_svr_init(args: &[Value]) -> Result { /// let args = scan_args(args)?; @@ -465,29 +462,28 @@ impl ScanArgsBlock for T where T: private::ScanArgsBlock {} /// let _: () = args.block; /// /// // ... -/// # let res = magnus::RArray::with_capacity(2); +/// # let res = Ruby::get().unwrap().ary_new_capa(2); /// # res.push(hostname)?; /// # res.push(port)?; -/// # Ok(res.into_value()) +/// # Ok(res.as_value()) /// } /// -/// let class = define_class("TCPServer", class::object()).unwrap(); -/// class -/// .define_singleton_method("new", function!(tcp_svr_init, -1)) -/// .unwrap(); -/// # let res = magnus::eval::(r#"TCPServer.new("foo", 1) == ["foo", 1]"#).unwrap(); -/// # assert!(res); -/// # let res = magnus::eval::(r#"TCPServer.new(2) == [nil, 2]"#).unwrap(); -/// # assert!(res); +/// 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::{ -/// class, define_class, error::Error, function, prelude::*, scan_args::scan_args, Value, -/// }; -/// # use magnus::IntoValue; -/// # let _cleanup = unsafe { magnus::embed::init() }; +/// 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)?; @@ -495,31 +491,30 @@ impl ScanArgsBlock for T where T: private::ScanArgsBlock {} /// let (port,) = args.trailing; /// /// // ... -/// # let res = magnus::RArray::with_capacity(2); +/// # let res = Ruby::get().unwrap().ary_new_capa(2); /// # res.push(hostname)?; /// # res.push(port)?; -/// # Ok(res.into_value()) +/// # Ok(res.as_value()) /// } /// -/// let class = define_class("TCPServer", class::object()).unwrap(); -/// class -/// .define_singleton_method("new", function!(tcp_svr_init, -1)) -/// .unwrap(); -/// # let res = magnus::eval::(r#"TCPServer.new("foo", 1) == ["foo", 1]"#).unwrap(); -/// # assert!(res); -/// # let res = magnus::eval::(r#"TCPServer.new(2) == [nil, 2]"#).unwrap(); -/// # assert!(res); +/// 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::{ -/// prelude::*, class, define_class, error::Error, function, scan_args::{scan_args, get_kwargs}, Symbol, Value, -/// }; -/// # use magnus::IntoValue; -/// # let _cleanup = unsafe { magnus::embed::init() }; +/// use magnus::{function, prelude::*, scan_args::{get_kwargs, scan_args}, Error, Ruby, Symbol, Value}; /// /// fn getaddrinfo(args: &[Value]) -> Result { /// let args = scan_args::<_, _, (), (), _, ()>(args)?; @@ -534,7 +529,7 @@ impl ScanArgsBlock for T where T: private::ScanArgsBlock {} /// let (timeout,) = kw.optional; /// /// // ... -/// # let res = magnus::RArray::with_capacity(7); +/// # let res = Ruby::get().unwrap().ary_new_capa(7); /// # res.push(nodename)?; /// # res.push(service)?; /// # res.push(family)?; @@ -542,15 +537,20 @@ impl ScanArgsBlock for T where T: private::ScanArgsBlock {} /// # res.push(protocol)?; /// # res.push(flags)?; /// # res.push(timeout)?; -/// # Ok(res.into_value()) +/// # Ok(res.as_value()) /// } /// -/// let class = define_class("Addrinfo", class::object()).unwrap(); -/// class.define_singleton_method("getaddrinfo", function!(getaddrinfo, -1)).unwrap(); -/// # let res = magnus::eval::(r#"Addrinfo.getaddrinfo("a", 1) == ["a", 1, nil, nil, nil, nil, nil]"#).unwrap(); -/// # assert!(res); -/// # let res = magnus::eval::(r#"Addrinfo.getaddrinfo("a", 1, :b, :c, 3, 4, timeout: 5) == ["a", 1, :b, :c, 3, 4, 5]"#).unwrap(); -/// # assert!(res); +/// 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], @@ -656,97 +656,102 @@ pub struct KwArgs { /// /// # Examples /// -/// The rough equivalent of `def example(a:, b:, c: nil, **rest)` would be: +/// The rough equivalent of `def test(a:, b:, c: nil, **rest)` would be: /// ``` -/// use magnus::{prelude::*, class, error::Error, method, scan_args::get_kwargs, RHash, Value}; +/// use magnus::{method, prelude::*, scan_args::get_kwargs, Error, RHash, Ruby, Value}; /// # use magnus::IntoValue; -/// # let _cleanup = unsafe { magnus::embed::init() }; /// -/// fn example(_rb_self: Value, kw: RHash) -> Result { +/// 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 = magnus::RArray::with_capacity(4); +/// # 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()) +/// # Ok(res.into_value_with(&Ruby::get().unwrap())) /// } /// -/// class::object().define_method("example", method!(example, 1)).unwrap(); -/// # let res = magnus::eval::(r#"Object.new.example(a: "foo", b: 1, c: true, d: "bar") == ["foo", 1, true, {d: "bar"}]"#).unwrap(); -/// # assert!(res); -/// # let res = magnus::eval::(r#"Object.new.example(a: "foo", b: 1) == ["foo", 1, nil, {}]"#).unwrap(); -/// # assert!(res); +/// 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 example(a: "foo")` would be: +/// The rough equivalent of `def test(a: "foo")` would be: /// ``` /// use magnus::{ -/// class, -/// error::Error, /// method, /// prelude::*, /// scan_args::{get_kwargs, scan_args}, -/// Value, +/// Error, Ruby, Value, /// }; /// # use magnus::IntoValue; -/// # let _cleanup = unsafe { magnus::embed::init() }; /// -/// fn example(_rb_self: Value, args: &[Value]) -> Result { +/// 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()) +/// # Ok(a.into_value_with(&Ruby::get().unwrap())) /// } /// -/// class::object() -/// .define_method("example", method!(example, -1)) -/// .unwrap(); -/// # let res = magnus::eval::(r#"Object.new.example(a: "test") == "test""#).unwrap(); -/// # assert!(res); -/// # let res = magnus::eval::(r#"Object.new.example == "foo""#).unwrap(); -/// # assert!(res); +/// 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::{ -/// class, -/// error::Error, /// method, /// prelude::*, /// scan_args::{get_kwargs, scan_args}, -/// RHash, Value, +/// Error, RHash, Ruby, Value, /// }; /// # use magnus::IntoValue; -/// # let _cleanup = unsafe { magnus::embed::init() }; /// -/// fn example(_rb_self: Value, args: &[Value]) -> Result { +/// 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()) +/// # Ok(a.into_value_with(&Ruby::get().unwrap())) /// } /// -/// class::object() -/// .define_method("example", method!(example, -1)) -/// .unwrap(); -/// # let res = magnus::eval::(r#"Object.new.example(a: "test") == "test""#).unwrap(); -/// # assert!(res); -/// # let res = magnus::eval::(r#"Object.new.example == "foo""#).unwrap(); -/// # assert!(res); +/// 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, @@ -875,6 +880,7 @@ impl Ruby { /// # Examples /// /// ``` +/// # #![allow(deprecated)] /// use magnus::{ /// define_global_function, eval, function, scan_args::check_arity, Error, RArray, RString, /// Value, @@ -900,9 +906,10 @@ impl Ruby { /// ); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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 diff --git a/src/symbol.rs b/src/symbol.rs index 1c157993..11c7247f 100644 --- a/src/symbol.rs +++ b/src/symbol.rs @@ -93,6 +93,7 @@ impl Symbol { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{rb_assert, Symbol}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -100,9 +101,10 @@ impl Symbol { /// rb_assert!(":example == sym", sym); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -116,12 +118,16 @@ impl Symbol { /// # Examples /// /// ``` - /// use magnus::{eval, Symbol}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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()); /// - /// assert!(eval::(":foo").unwrap().is_static()); - /// assert!(!Symbol::new("bar").is_static()); - /// assert!(!eval::(r#""baz".to_sym"#).unwrap().is_static()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn is_static(self) -> bool { if self.is_static_symbol() { @@ -139,11 +145,15 @@ impl Symbol { /// # Examples /// /// ``` - /// use magnus::Symbol; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let sym = Symbol::new("example"); - /// assert_eq!(sym.name().unwrap(), "example"); + /// 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() { @@ -162,15 +172,19 @@ impl Symbol { /// # Examples /// /// ``` - /// use magnus::{eval, Symbol}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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()); /// - /// assert!(eval::(":foo").unwrap().as_static().is_some()); - /// assert!(Symbol::new("bar").as_static().is_none()); - /// assert!(eval::(r#""baz".to_sym"#) - /// .unwrap() - /// .as_static() - /// .is_none()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn as_static(self) -> Option { self.is_static() @@ -186,12 +200,16 @@ impl Symbol { /// # Examples /// /// ``` - /// use magnus::{rb_assert, Symbol}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{rb_assert, Error, Ruby}; /// - /// let sym = Symbol::new("example"); - /// let static_sym = sym.to_static(); - /// rb_assert!("sym == static_sym", sym, static_sym); + /// 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()) { @@ -230,6 +248,7 @@ pub trait IntoSymbol: Sized { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{rb_assert, symbol::IntoSymbol}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -237,9 +256,10 @@ pub trait IntoSymbol: Sized { /// rb_assert!("sym == :example", sym); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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!()) diff --git a/src/thread.rs b/src/thread.rs new file mode 100644 index 00000000..cc17a607 --- /dev/null +++ b/src/thread.rs @@ -0,0 +1,684 @@ +use 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) } +} diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 00000000..75b32d19 --- /dev/null +++ b/src/time.rs @@ -0,0 +1,190 @@ +use 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", + )) + } + } +} diff --git a/src/typed_data.rs b/src/typed_data.rs index 5bb940ff..953dc846 100644 --- a/src/typed_data.rs +++ b/src/typed_data.rs @@ -329,7 +329,6 @@ where } else { None }; - #[cfg(ruby_gte_2_7)] let dcompact = if self.compact { Some(T::extern_compact as _) } else { @@ -341,12 +340,8 @@ where dmark, dfree, dsize, - #[cfg(ruby_gte_2_7)] dcompact, - #[cfg(ruby_gte_2_7)] reserved: [ptr::null_mut(); 1], - #[cfg(ruby_lt_2_7)] - reserved: [ptr::null_mut(); 2], }, parent: ptr::null(), data: ptr::null_mut(), @@ -533,7 +528,7 @@ where /// 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, rather than on [`Obj::get`]. +/// 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 @@ -698,6 +693,7 @@ where /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{class, define_class, prelude::*, typed_data}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -714,9 +710,10 @@ where /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -737,6 +734,7 @@ where /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{class, define_class, prelude::*, typed_data}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -762,6 +760,7 @@ where /// `new` method rather than `initialize`) /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{ /// class, define_class, eval, function, method, prelude::*, typed_data, RClass, Value, /// }; @@ -800,22 +799,14 @@ where /// # let _ = Point { x: 1, y: 2 }.x + Point { x: 3, y: 4 }.y; /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) } - - #[doc(hidden)] - #[deprecated( - since = "0.6.0", - note = "Obj::get() is unnecessary, Obj derefs to T" - )] - pub fn get(&self) -> &T { - self.inner.get().unwrap() - } } impl Deref for Obj @@ -829,8 +820,7 @@ where /// # Examples /// /// ``` - /// use magnus::{class, define_class, typed_data}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// /// #[magnus::wrap(class = "Point")] /// #[derive(Debug, PartialEq, Eq)] @@ -839,10 +829,15 @@ where /// y: isize, /// } /// - /// define_class("Point", class::object()).unwrap(); - /// let value = typed_data::Obj::wrap(Point { x: 4, y: 2 }); + /// 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 }); /// - /// assert_eq!(&*value, &Point { x: 4, y: 2 }); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn deref(&self) -> &Self::Target { self.inner.get().unwrap() @@ -931,8 +926,8 @@ where /// use std::hash::Hasher; /// /// use magnus::{ -/// class, define_class, function, gc, method, prelude::*, typed_data, value::Opaque, -/// DataTypeFunctions, IntoValue, RHash, TypedData, Value, +/// function, gc, method, prelude::*, typed_data, value::Opaque, DataTypeFunctions, Error, +/// Ruby, TypedData, Value, /// }; /// /// #[derive(TypedData)] @@ -987,28 +982,34 @@ where /// /// impl Eq for Pair {} /// -/// # let _cleanup = unsafe { magnus::embed::init() }; -/// # -/// let class = define_class("Pair", class::object()).unwrap(); -/// class -/// .define_singleton_method("new", function!(Pair::new, 2)) -/// .unwrap(); -/// class -/// .define_method("hash", method!(::hash, 0)) -/// .unwrap(); -/// class -/// .define_method("eql?", method!(::is_eql, 1)) -/// .unwrap(); -/// -/// let a = Pair::new("foo".into_value(), 1.into_value()); -/// let hash = RHash::new(); -/// hash.aset(a, "test value").unwrap(); -/// -/// let b = Pair::new("foo".into_value(), 1.into_value()); -/// assert_eq!("test value", hash.fetch::<_, String>(b).unwrap()); -/// -/// let c = Pair::new("bar".into_value(), 2.into_value()); -/// assert!(hash.get(c).is_none()); +/// 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. @@ -1043,8 +1044,8 @@ where /// use std::hash::Hasher; /// /// use magnus::{ -/// class, define_class, function, gc, method, prelude::*, typed_data, value::Opaque, -/// DataTypeFunctions, IntoValue, RHash, TypedData, Value, +/// function, gc, method, prelude::*, typed_data, value::Opaque, DataTypeFunctions, Error, +/// Ruby, TypedData, Value, /// }; /// /// #[derive(TypedData)] @@ -1099,28 +1100,34 @@ where /// /// impl Eq for Pair {} /// -/// # let _cleanup = unsafe { magnus::embed::init() }; -/// # -/// let class = define_class("Pair", class::object()).unwrap(); -/// class -/// .define_singleton_method("new", function!(Pair::new, 2)) -/// .unwrap(); -/// class -/// .define_method("hash", method!(::hash, 0)) -/// .unwrap(); -/// class -/// .define_method("eql?", method!(::is_eql, 1)) -/// .unwrap(); -/// -/// let a = Pair::new("foo".into_value(), 1.into_value()); -/// let hash = RHash::new(); -/// hash.aset(a, "test value").unwrap(); -/// -/// let b = Pair::new("foo".into_value(), 1.into_value()); -/// assert_eq!("test value", hash.fetch::<_, String>(b).unwrap()); -/// -/// let c = Pair::new("bar".into_value(), 2.into_value()); -/// assert!(hash.get(c).is_none()); +/// 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. @@ -1153,8 +1160,8 @@ where /// use std::cmp::Ordering; /// /// use magnus::{ -/// class, define_class, function, gc, method, module, prelude::*, rb_assert, typed_data, -/// value::Opaque, DataTypeFunctions, IntoValue, Module, TypedData, Value, +/// function, gc, method, prelude::*, rb_assert, typed_data, value::Opaque, DataTypeFunctions, +/// Error, Module, Ruby, TypedData, Value, /// }; /// /// #[derive(TypedData)] @@ -1206,28 +1213,45 @@ where /// } /// } /// -/// # let _cleanup = unsafe { magnus::embed::init() }; -/// # -/// let class = define_class("Pair", class::object()).unwrap(); -/// class -/// .define_singleton_method("new", function!(Pair::new, 2)) -/// .unwrap(); -/// class -/// .define_method("<=>", method!(::cmp, 1)) -/// .unwrap(); -/// class.include_module(module::comparable()).unwrap(); -/// -/// let a = Pair::new("foo".into_value(), 1.into_value()); -/// let b = Pair::new("foo".into_value(), 2.into_value()); -/// rb_assert!("a < b", a, b); -/// -/// let b = Pair::new("foo".into_value(), 2.into_value()); -/// let c = Pair::new("bar".into_value(), 3.into_value()); -/// rb_assert!("b > c", b, c); -/// -/// let a = Pair::new("foo".into_value(), 1.into_value()); -/// let b = Pair::new("foo".into_value(), 2.into_value()); -/// rb_assert!("(a <=> b) == -1", a, 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("<=>", 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. @@ -1261,8 +1285,8 @@ where /// use std::fmt; /// /// use magnus::{ -/// class, define_class, function, gc, method, prelude::*, rb_assert, typed_data, -/// value::Opaque, DataTypeFunctions, IntoValue, TypedData, Value, +/// function, gc, method, prelude::*, rb_assert, typed_data, value::Opaque, DataTypeFunctions, +/// Error, Ruby, TypedData, Value, /// }; /// /// #[derive(TypedData)] @@ -1299,21 +1323,23 @@ where /// } /// } /// -/// # let _cleanup = unsafe { magnus::embed::init() }; -/// # -/// let class = define_class("Pair", class::object()).unwrap(); -/// class -/// .define_singleton_method("new", function!(Pair::new, 2)) -/// .unwrap(); -/// class -/// .define_method( +/// 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), -/// ) -/// .unwrap(); +/// )?; /// -/// let pair = Pair::new("foo".into_value(), 1.into_value()); -/// rb_assert!(r#"pair.inspect == "Pair { a: \"foo\", b: 1 }""#, pair); +/// 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. @@ -1341,8 +1367,8 @@ where /// /// ``` /// use magnus::{ -/// class, define_class, function, gc, method, prelude::*, rb_assert, typed_data, -/// value::Opaque, DataTypeFunctions, IntoValue, TypedData, Value, +/// function, gc, method, prelude::*, rb_assert, typed_data, value::Opaque, DataTypeFunctions, +/// Error, Ruby, TypedData, Value, /// }; /// /// #[derive(TypedData, Clone)] @@ -1368,21 +1394,21 @@ where /// } /// } /// -/// # let _cleanup = unsafe { magnus::embed::init() }; -/// # -/// let class = define_class("Pair", 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 a = Pair::new("foo".into_value(), 1.into_value()); -/// rb_assert!("b = a.dup; a.object_id != b.object_id", a); +/// 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. diff --git a/src/value.rs b/src/value.rs index a200a9d0..9067e466 100644 --- a/src/value.rs +++ b/src/value.rs @@ -4,14 +4,13 @@ #[cfg(ruby_use_flonum)] mod flonum; -#[cfg(not(feature = "deprecated-send-sync-value"))] -use std::marker::PhantomData; use std::{ borrow::{Borrow, Cow}, cell::UnsafeCell, ffi::CStr, fmt, hash::{Hash, Hasher}, + marker::PhantomData, mem::transmute, num::NonZeroUsize, ops::{Deref, DerefMut}, @@ -60,28 +59,11 @@ use crate::{ /// /// Methods for `Value` are implemented on the [`ReprValue`] trait, which is /// also implemented for all Ruby types. -#[cfg(feature = "deprecated-send-sync-value")] -#[derive(Clone, Copy)] -#[repr(transparent)] -pub struct Value(VALUE); - -/// 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. -#[cfg(not(feature = "deprecated-send-sync-value"))] #[derive(Clone, Copy)] #[repr(transparent)] pub struct Value(VALUE, PhantomData<*mut RBasic>); impl Value { - #[cfg(feature = "deprecated-send-sync-value")] - #[inline] - pub(crate) const fn new(val: VALUE) -> Self { - Self(val) - } - - #[cfg(not(feature = "deprecated-send-sync-value"))] #[inline] pub(crate) const fn new(val: VALUE) -> Self { Self(val, PhantomData) @@ -91,18 +73,6 @@ impl Value { pub(crate) const fn as_rb_value(self) -> VALUE { self.0 } - - #[doc(hidden)] - #[deprecated( - since = "0.6.0", - note = "please use `TryConvert::try_convert` or `T::try_convert` instead" - )] - pub fn try_convert(self) -> Result - where - T: TryConvert, - { - T::try_convert(self.as_value()) - } } impl fmt::Display for Value { @@ -484,12 +454,8 @@ unsafe impl Sync for Opaque {} pub struct Lazy { init: Once, mark: bool, - inner: UnsafeCell>, -} - -union LazyInner { func: fn(&Ruby) -> T, - value: T, + value: UnsafeCell, } impl Lazy @@ -499,7 +465,9 @@ where /// Create a new `Lazy`. /// /// This function can be called in a `const` context. `func` is evaluated - /// once when the `Lazy` is first accessed (see [`Ruby::get_inner`]). + /// 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 @@ -521,7 +489,8 @@ where Self { init: Once::new(), mark: true, - inner: UnsafeCell::new(LazyInner { func }), + func, + value: UnsafeCell::new(QNIL.0.get()), } } @@ -536,7 +505,8 @@ where Self { init: Once::new(), mark: false, - inner: UnsafeCell::new(LazyInner { func }), + func, + value: UnsafeCell::new(QNIL.0.get()), } } @@ -592,7 +562,7 @@ where unsafe { this.init .is_completed() - .then(|| (*this.inner.get()).value.into()) + .then(|| T::from_value_unchecked(*this.value.get()).into()) } } } @@ -619,15 +589,16 @@ where fn get_inner_ref_with<'a>(&'a self, ruby: &Ruby) -> &'a Self::Value { unsafe { - self.init.call_once(|| { - let inner = self.inner.get(); - let value = ((*inner).func)(ruby); - if self.mark { - gc::register_mark_object(value); - } - (*inner).value = value; - }); - &(*self.inner.get()).value + 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()) } } } @@ -656,6 +627,11 @@ pub(crate) mod private { *(&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 @@ -814,13 +790,17 @@ pub trait ReprValue: private::ReprValue { /// # Examples /// /// ``` - /// use magnus::{eval, prelude::*, Value}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, Ruby, Value}; /// - /// assert!(eval::("nil").unwrap().is_nil()); - /// assert!(!eval::("Object.new").unwrap().is_nil()); - /// assert!(!eval::("0").unwrap().is_nil()); - /// assert!(!eval::("[]").unwrap().is_nil()); + /// 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 { @@ -837,36 +817,44 @@ pub trait ReprValue: private::ReprValue { /// # Examples /// /// ``` - /// use magnus::{prelude::*, Integer, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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)?); /// - /// let a = RArray::from_vec(vec![1, 2, 3]); - /// let b = RArray::from_vec(vec![1, 2, 3]); - /// let c = RArray::from_vec(vec![4, 5, 6]); - /// let d = Integer::from_i64(1); - /// assert!(a.equal(a).unwrap()); - /// assert!(a.equal(b).unwrap()); - /// assert!(!a.equal(c).unwrap()); - /// assert!(!a.equal(d).unwrap()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` /// /// ``` - /// use magnus::{eval, prelude::*, Value}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{eval, prelude::*, Error, Ruby, Value}; /// - /// let (a, b): (Value, Value) = eval!( - /// " - /// class Example - /// def ==(other) - /// raise - /// end - /// end - /// [Example.new, Example.new] - /// " - /// ) - /// .unwrap(); + /// 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()); /// - /// assert!(a.equal(b).is_err()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn equal(self, other: T) -> Result where @@ -890,36 +878,44 @@ pub trait ReprValue: private::ReprValue { /// # Examples /// /// ``` - /// use magnus::{prelude::*, Integer, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, Ruby}; /// - /// let a = RArray::from_vec(vec![1, 2, 3]); - /// let b = RArray::from_vec(vec![1, 2, 3]); - /// let c = RArray::from_vec(vec![4, 5, 6]); - /// let d = Integer::from_i64(1); - /// assert!(a.eql(a).unwrap()); - /// assert!(a.eql(b).unwrap()); - /// assert!(!a.eql(c).unwrap()); - /// assert!(!a.eql(d).unwrap()); + /// 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::*, Value}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{eval, prelude::*, Error, Ruby, Value}; /// - /// let (a, b): (Value, Value) = eval!( - /// " - /// class Example - /// def eql?(other) - /// raise - /// end - /// end - /// [Example.new, Example.new] - /// " - /// ) - /// .unwrap(); + /// 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()); /// - /// assert!(a.eql(b).is_err()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn eql(self, other: T) -> Result where @@ -945,14 +941,17 @@ pub trait ReprValue: private::ReprValue { /// # Examples /// /// ``` - /// use magnus::{prelude::*, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, Ruby}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// assert!(ruby + /// .str_new("test") + /// .hash()? + /// .equal(ruby.str_new("test").hash()?)?); /// - /// assert!(RString::new("test") - /// .hash() - /// .unwrap() - /// .equal(RString::new("test").hash().unwrap()) - /// .unwrap()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn hash(self) -> Result { unsafe { protect(|| Integer::from_rb_value_unchecked(rb_hash(self.as_rb_value()))) } @@ -967,14 +966,15 @@ pub trait ReprValue: private::ReprValue { /// # Examples /// /// ``` - /// use magnus::{eval, prelude::*, Value}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, Ruby, Value}; /// - /// assert_eq!( - /// eval::("true").unwrap().class().inspect(), - /// "TrueClass" - /// ); - /// assert_eq!(eval::("[1,2,3]").unwrap().class().inspect(), "Array"); + /// 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); @@ -1011,12 +1011,16 @@ pub trait ReprValue: private::ReprValue { /// # Examples /// /// ``` - /// use magnus::{eval, prelude::*, Value}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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()); /// - /// assert!(eval::(":foo").unwrap().is_frozen()); - /// assert!(eval::("42").unwrap().is_frozen()); - /// assert!(!eval::("[]").unwrap().is_frozen()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn is_frozen(self) -> bool { match self.r_basic() { @@ -1034,8 +1038,7 @@ pub trait ReprValue: private::ReprValue { /// /// # Examples /// ``` - /// use magnus::{eval, prelude::*, Error, Value}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, Ruby, Value}; /// /// fn mutate(val: Value) -> Result<(), Error> { /// val.check_frozen()?; @@ -1044,8 +1047,13 @@ pub trait ReprValue: private::ReprValue { /// Ok(()) /// } /// - /// assert!(mutate(eval("Object.new").unwrap()).is_ok()); - /// assert!(mutate(eval(":foo").unwrap()).is_err()); + /// 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() { @@ -1063,13 +1071,17 @@ pub trait ReprValue: private::ReprValue { /// # Examples /// /// ``` - /// use magnus::{prelude::*, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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()); /// - /// let ary = RArray::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()) }; @@ -1081,17 +1093,21 @@ pub trait ReprValue: private::ReprValue { /// # Examples /// /// ``` - /// use magnus::{eval, prelude::*, Value}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, Ruby, Value}; /// - /// assert!(!eval::("false").unwrap().to_bool()); - /// assert!(!eval::("nil").unwrap().to_bool()); + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// assert!(!ruby.eval::("false")?.to_bool()); + /// assert!(!ruby.eval::("nil")?.to_bool()); /// - /// assert!(eval::("true").unwrap().to_bool()); - /// assert!(eval::("0").unwrap().to_bool()); - /// assert!(eval::("[]").unwrap().to_bool()); - /// assert!(eval::(":foo").unwrap().to_bool()); - /// assert!(eval::("Object.new").unwrap().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 { @@ -1107,33 +1123,41 @@ pub trait ReprValue: private::ReprValue { /// # Examples /// /// ``` - /// use magnus::{eval, prelude::*, RArray}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, RArray, Ruby}; /// - /// let values = eval::(r#"["foo", 1, :bar]"#).unwrap(); - /// let result: String = values.funcall("join", (" & ",)).unwrap(); - /// assert_eq!(result, "foo & 1 & bar"); - /// ``` + /// 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::*, RObject}; - /// # let _cleanup = unsafe { magnus::embed::init() }; /// - /// let object: RObject = eval!( - /// r#" - /// class Adder - /// def add(a, b, c:) - /// a + b + c - /// end - /// end + /// ``` + /// use magnus::{eval, kwargs, prelude::*, Error, RObject, Ruby}; /// - /// Adder.new - /// "# - /// ) - /// .unwrap(); + /// 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); /// - /// let result: i32 = object.funcall("add", (1, 2, kwargs!("c" => 3))).unwrap(); - /// assert_eq!(result, 6); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn funcall(self, method: M, args: A) -> Result where @@ -1169,33 +1193,37 @@ pub trait ReprValue: private::ReprValue { /// # Examples /// /// ``` - /// use magnus::{eval, prelude::*, Error, RObject, Symbol}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{eval, prelude::*, Error, RObject, Ruby, Symbol}; /// - /// let object: RObject = eval!( - /// r#" - /// class Foo - /// def bar - /// :bar - /// end + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let object: RObject = eval!( + /// ruby, + /// r#" + /// class Foo + /// def bar + /// :bar + /// end + /// + /// private /// - /// private + /// def baz + /// :baz + /// end + /// end /// - /// def baz - /// :baz - /// end - /// end + /// Foo.new + /// "# + /// )?; /// - /// Foo.new - /// "# - /// ) - /// .unwrap(); + /// let result: Symbol = object.funcall_public("bar", ())?; + /// assert_eq!(result.name()?, "bar"); /// - /// let result: Symbol = object.funcall_public("bar", ()).unwrap(); - /// assert_eq!(result.name().unwrap(), "bar"); + /// let result: Result = object.funcall_public("baz", ()); + /// assert!(result.is_err()); /// - /// 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 @@ -1230,16 +1258,20 @@ pub trait ReprValue: private::ReprValue { /// # Examples /// /// ``` - /// use magnus::{prelude::*, Float, Integer, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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 = Float::from_f64(1.23); - /// let res: Integer = val.check_funcall("to_int", ()).unwrap().unwrap(); - /// assert_eq!(res.to_i64().unwrap(), 1); + /// let val = ruby.str_new("1.23"); + /// let res: Option> = val.check_funcall("to_int", ()); + /// assert!(res.is_none()); /// - /// let val = RString::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 @@ -1280,13 +1312,17 @@ pub trait ReprValue: private::ReprValue { /// # Examples /// /// ``` - /// use magnus::{block::Proc, eval, prelude::*, RArray, Value}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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"]); /// - /// let values = eval::(r#"["foo", 1, :bar]"#).unwrap(); - /// let block = Proc::new(|args, _block| args.first().unwrap().to_r_string()); - /// let _: Value = values.funcall_with_block("map!", (), block).unwrap(); - /// assert_eq!(values.to_vec::().unwrap(), vec!["foo", "1", "bar"]); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn funcall_with_block(self, method: M, args: A, block: Proc) -> Result where @@ -1335,22 +1371,24 @@ pub trait ReprValue: private::ReprValue { /// # Examples /// /// ``` - /// use magnus::{eval, prelude::*, RArray, Value}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, RArray, Ruby, Value}; /// - /// let values = eval::(r#"["foo", 1, :bar]"#).unwrap(); - /// let _: Value = values - /// .block_call("map!", (), |args, _block| { + /// 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() - /// }) - /// .unwrap(); - /// assert_eq!(values.to_vec::().unwrap(), vec!["foo", "1", "bar"]); + /// })?; + /// assert_eq!(values.to_vec::()?, vec!["foo", "1", "bar"]); + /// + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn block_call( self, method: M, args: A, - block: fn(&[Value], Option) -> R, + block: fn(&Ruby, &[Value], Option) -> R, ) -> Result where M: IntoId, @@ -1368,7 +1406,8 @@ pub trait ReprValue: private::ReprValue { where R: BlockReturn, { - let func = std::mem::transmute::) -> R>(callback_arg); + let func = + std::mem::transmute::) -> R>(callback_arg); func.call_handle_error(argc, argv as *const Value, Value::new(blockarg)) .as_rb_value() } @@ -1380,8 +1419,6 @@ pub trait ReprValue: private::ReprValue { let slice = args.as_ref(); let call_func = call:: as unsafe extern "C" fn(VALUE, VALUE, c_int, *const VALUE, VALUE) -> VALUE; - #[cfg(ruby_lt_2_7)] - let call_func: unsafe extern "C" fn() -> VALUE = unsafe { std::mem::transmute(call_func) }; protect(|| unsafe { #[allow(clippy::fn_to_numeric_cast)] @@ -1408,15 +1445,19 @@ pub trait ReprValue: private::ReprValue { /// # Examples /// /// ``` - /// use magnus::{prelude::*, RString}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, Ruby}; /// - /// let s = RString::new("example"); - /// assert!(s.respond_to("to_str", false).unwrap()); - /// assert!(!s.respond_to("puts", false).unwrap()); - /// assert!(s.respond_to("puts", true).unwrap()); - /// assert!(!s.respond_to("non_existant", false).unwrap()); - /// assert!(!s.respond_to("non_existant", true).unwrap()); + /// 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 @@ -1443,11 +1484,15 @@ pub trait ReprValue: private::ReprValue { /// # Examples /// /// ``` - /// use magnus::{class, eval, prelude::*, Value}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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())); /// - /// let value = eval::("[]").unwrap(); - /// assert!(value.to_r_string().unwrap().is_kind_of(class::string())); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn to_r_string(self) -> Result { match RString::from_value(self.as_value()) { @@ -1472,13 +1517,17 @@ pub trait ReprValue: private::ReprValue { /// # Examples /// /// ``` - /// use magnus::{prelude::*, IntoValue}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, Ruby}; /// - /// let value = true.into_value(); - /// // safe as we never give Ruby a chance to free the string. - /// let s = unsafe { value.to_s() }.unwrap().into_owned(); - /// assert_eq!(s, "true"); + /// 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> { @@ -1498,11 +1547,15 @@ pub trait ReprValue: private::ReprValue { /// # Examples /// /// ``` - /// use magnus::{prelude::*, IntoValue, Symbol}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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"); /// - /// assert_eq!(().into_value().inspect(), "nil"); - /// assert_eq!(Symbol::new("foo").inspect(), ":foo"); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn inspect(self) -> String { let handle = Ruby::get_with(self); @@ -1531,13 +1584,17 @@ pub trait ReprValue: private::ReprValue { /// # Examples /// /// ``` - /// use magnus::{prelude::*, RHash}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, Ruby}; /// - /// let value = RHash::new(); - /// // safe as we never give Ruby a chance to free the string. - /// let s = unsafe { value.classname() }.into_owned(); - /// assert_eq!(s, "Hash"); + /// 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()); @@ -1550,11 +1607,15 @@ pub trait ReprValue: private::ReprValue { /// # Examples /// /// ``` - /// use magnus::{class, eval, prelude::*, Value}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, Error, Ruby, Value}; + /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// let value = ruby.eval::("[]")?; + /// assert!(value.is_kind_of(ruby.class_array())); /// - /// let value = eval::("[]").unwrap(); - /// assert!(value.is_kind_of(class::array())); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` fn is_kind_of(self, class: T) -> bool where @@ -1569,16 +1630,20 @@ pub trait ReprValue: private::ReprValue { /// # Examples /// /// ``` - /// use magnus::{class, prelude::*, r_string}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{prelude::*, r_string, Error, Ruby}; /// - /// let s = r_string!("foo\\bar\\baz"); - /// let mut i = 0; - /// for line in s.enumeratorize("each_line", ("\\",)) { - /// assert!(line.unwrap().is_kind_of(class::string())); - /// i += 1; + /// 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(()) /// } - /// assert_eq!(i, 3); + /// # Ruby::init(example).unwrap() /// ``` fn enumeratorize(self, method: M, args: A) -> Enumerator where @@ -1606,24 +1671,11 @@ unsafe impl private::ReprValue for Value {} impl ReprValue for Value {} -#[cfg(feature = "deprecated-send-sync-value")] -#[derive(Clone, Copy, Eq, Hash, PartialEq)] -#[repr(transparent)] -pub(crate) struct NonZeroValue(NonZeroUsize); - -#[cfg(not(feature = "deprecated-send-sync-value"))] #[derive(Clone, Copy, Eq, Hash, PartialEq)] #[repr(transparent)] pub(crate) struct NonZeroValue(NonZeroUsize, PhantomData>); impl NonZeroValue { - #[cfg(feature = "deprecated-send-sync-value")] - #[inline] - pub(crate) const unsafe fn new_unchecked(val: Value) -> Self { - Self(NonZeroUsize::new_unchecked(val.as_rb_value() as usize)) - } - - #[cfg(not(feature = "deprecated-send-sync-value"))] #[inline] pub(crate) const unsafe fn new_unchecked(val: Value) -> Self { Self( @@ -1640,8 +1692,8 @@ impl NonZeroValue { /// Protects a Ruby Value from the garbage collector. /// -/// See also [`gc::register_mark_object`](crate::gc::register_mark_object) for -/// a value that should be permanently excluded from garbage collection. +/// See also [`gc::register_mark_object`] for a value that should be +/// permanently excluded from garbage collection. pub struct BoxValue(Box); impl BoxValue @@ -1653,30 +1705,34 @@ where /// # Examples /// /// ``` - /// use magnus::{eval, gc, value::BoxValue, RString, Value}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{eval, value::BoxValue, Error, RString, Ruby, Value}; /// /// # #[inline(never)] - /// fn box_value() -> BoxValue { - /// BoxValue::new(RString::new("foo")) + /// fn box_value(ruby: &Ruby) -> BoxValue { + /// BoxValue::new(ruby.str_new("foo")) /// } /// - /// # // 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(); + /// 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(); /// - /// # // make some garbage - /// # eval::(r#"1024.times.map {|i| "test#{i}"}"#).unwrap(); - /// // run garbage collector - /// gc::start(); + /// # // try and use value + /// // boxed is still useable + /// let result: String = eval!(ruby, r#"foo + "bar""#, foo = boxed)?; /// - /// # // try and use value - /// // boxed is still useable - /// let result: String = eval!(r#"foo + "bar""#, foo = boxed).unwrap(); + /// assert_eq!(result, "foobar"); /// - /// assert_eq!(result, "foobar"); + /// # // didn't segfault? we passed! /// - /// # // didn't segfault? we passed! + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` pub fn new(val: T) -> Self { let mut boxed = Box::new(val); @@ -1775,7 +1831,6 @@ impl Ruby { /// ``` #[inline] pub fn qfalse(&self) -> Qfalse { - #[allow(deprecated)] QFALSE } } @@ -1790,11 +1845,7 @@ impl Ruby { pub struct Qfalse(Value); /// Ruby's `false` value. -#[deprecated( - since = "0.6.0", - note = "please use `value::qfalse`/`Ruby::qfalse` instead" -)] -pub const QFALSE: Qfalse = Qfalse::new(); +const QFALSE: Qfalse = Qfalse::new(); /// Returns Ruby's `false` value. /// @@ -1808,15 +1859,17 @@ pub const QFALSE: Qfalse = Qfalse::new(); /// # 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 = "friendly-api"), + 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() @@ -1907,7 +1960,6 @@ impl Ruby { /// ``` #[inline] pub fn qnil(&self) -> Qnil { - #[allow(deprecated)] QNIL } } @@ -1922,11 +1974,7 @@ impl Ruby { pub struct Qnil(NonZeroValue); /// Ruby's `nil` value. -#[deprecated( - since = "0.6.0", - note = "please use `value::qnil`/`Ruby::qnil` instead" -)] -pub const QNIL: Qnil = Qnil::new(); +const QNIL: Qnil = Qnil::new(); /// Returns Ruby's `nil` value. /// @@ -1940,15 +1988,17 @@ pub const QNIL: Qnil = Qnil::new(); /// # 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 = "friendly-api"), + 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() @@ -2067,7 +2117,6 @@ impl Ruby { /// ``` #[inline] pub fn qtrue(&self) -> Qtrue { - #[allow(deprecated)] QTRUE } } @@ -2082,11 +2131,7 @@ impl Ruby { pub struct Qtrue(NonZeroValue); /// Ruby's `true` value. -#[deprecated( - since = "0.6.0", - note = "please use `value::qtrue`/`Ruby::qtrue` instead" -)] -pub const QTRUE: Qtrue = Qtrue::new(); +const QTRUE: Qtrue = Qtrue::new(); /// Returns Ruby's `true` value. /// @@ -2100,15 +2145,17 @@ pub const QTRUE: Qtrue = Qtrue::new(); /// # 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 = "friendly-api"), + 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() @@ -2243,12 +2290,6 @@ impl Qundef { pub unsafe fn as_value(self) -> Value { self.0.get() } - - #[doc(hidden)] - #[deprecated(since = "0.6.0", note = "please use `as_value` instead")] - pub unsafe fn to_value(self) -> Value { - self.as_value() - } } /// # `Fixnum` @@ -2377,6 +2418,7 @@ impl Fixnum { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::Fixnum; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -2386,9 +2428,10 @@ impl Fixnum { /// assert!(Fixnum::from_i64(-4611686018427387905).is_err()); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -2407,6 +2450,7 @@ impl Fixnum { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::Fixnum; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -2415,9 +2459,10 @@ impl Fixnum { /// assert!(Fixnum::from_u64(4611686018427387904).is_err()); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -2433,13 +2478,17 @@ impl Fixnum { /// # Examples /// /// ``` - /// use magnus::{eval, Fixnum}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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()); /// - /// 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()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn to_i8(self) -> Result { @@ -2459,13 +2508,17 @@ impl Fixnum { /// # Examples /// /// ``` - /// use magnus::{eval, Fixnum}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Fixnum, Ruby}; /// - /// 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()); + /// 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 { @@ -2485,22 +2538,20 @@ impl Fixnum { /// # Examples /// /// ``` - /// use magnus::{eval, Fixnum}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Fixnum, Ruby}; /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { /// # #[cfg(not(windows))] /// # { - /// 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()); + /// 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 { @@ -2520,19 +2571,23 @@ impl Fixnum { /// # Examples /// /// ``` - /// use magnus::{eval, Fixnum}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Fixnum, Ruby}; /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { /// # #[cfg(not(windows))] - /// assert_eq!( - /// eval::("4611686018427387903").unwrap().to_i64(), - /// 4611686018427387903 - /// ); + /// assert_eq!( + /// ruby.eval::("4611686018427387903")?.to_i64(), + /// 4611686018427387903 + /// ); /// # #[cfg(not(windows))] - /// assert_eq!( - /// eval::("-4611686018427387904").unwrap().to_i64(), - /// -4611686018427387904 - /// ); + /// assert_eq!( + /// ruby.eval::("-4611686018427387904")?.to_i64(), + /// -4611686018427387904 + /// ); + /// + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn to_i64(self) -> i64 { @@ -2545,19 +2600,23 @@ impl Fixnum { /// # Examples /// /// ``` - /// use magnus::{eval, Fixnum}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Fixnum, Ruby}; /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { /// # #[cfg(not(windows))] - /// assert_eq!( - /// eval::("4611686018427387903").unwrap().to_isize(), - /// 4611686018427387903 - /// ); + /// assert_eq!( + /// ruby.eval::("4611686018427387903")?.to_isize(), + /// 4611686018427387903 + /// ); /// # #[cfg(not(windows))] - /// assert_eq!( - /// eval::("-4611686018427387904").unwrap().to_isize(), - /// -4611686018427387904 - /// ); + /// assert_eq!( + /// ruby.eval::("-4611686018427387904")?.to_isize(), + /// -4611686018427387904 + /// ); + /// + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn to_isize(self) -> isize { @@ -2570,12 +2629,16 @@ impl Fixnum { /// # Examples /// /// ``` - /// use magnus::{eval, Fixnum}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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()); /// - /// 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()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn to_u8(self) -> Result { @@ -2602,12 +2665,16 @@ impl Fixnum { /// # Examples /// /// ``` - /// use magnus::{eval, Fixnum}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// 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()); /// - /// 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()); + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn to_u16(self) -> Result { @@ -2634,18 +2701,19 @@ impl Fixnum { /// # Examples /// /// ``` - /// use magnus::{eval, Fixnum}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Fixnum, Ruby}; /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { /// # #[cfg(not(windows))] /// # { - /// assert_eq!( - /// eval::("4294967295").unwrap().to_u32().unwrap(), - /// 4294967295 - /// ); - /// assert!(eval::("4294967296").unwrap().to_u32().is_err()); + /// assert_eq!(ruby.eval::("4294967295")?.to_u32()?, 4294967295); + /// assert!(ruby.eval::("4294967296")?.to_u32().is_err()); /// # } - /// assert!(eval::("-1").unwrap().to_u32().is_err()); + /// assert!(ruby.eval::("-1")?.to_u32().is_err()); + /// + /// Ok(()) + /// } + /// # Ruby::init(example).unwrap() /// ``` #[inline] pub fn to_u32(self) -> Result { @@ -2671,18 +2739,19 @@ impl Fixnum { /// # Examples /// /// ``` - /// use magnus::{eval, Fixnum}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Fixnum, Ruby}; /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { /// # #[cfg(not(windows))] - /// assert_eq!( - /// eval::("4611686018427387903") - /// .unwrap() - /// .to_u64() - /// .unwrap(), - /// 4611686018427387903 - /// ); - /// assert!(eval::("-1").unwrap().to_u64().is_err()); + /// 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 { @@ -2701,18 +2770,19 @@ impl Fixnum { /// # Examples /// /// ``` - /// use magnus::{eval, Fixnum}; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Fixnum, Ruby}; /// + /// fn example(ruby: &Ruby) -> Result<(), Error> { /// # #[cfg(not(windows))] - /// assert_eq!( - /// eval::("4611686018427387903") - /// .unwrap() - /// .to_usize() - /// .unwrap(), - /// 4611686018427387903 - /// ); - /// assert!(eval::("-1").unwrap().to_usize().is_err()); + /// 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 { @@ -2878,6 +2948,7 @@ impl StaticSymbol { /// /// # Examples /// ``` + /// # #![allow(deprecated)] /// use magnus::{rb_assert, StaticSymbol}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -2885,9 +2956,10 @@ impl StaticSymbol { /// rb_assert!(":example == sym", sym); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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 @@ -2906,6 +2978,7 @@ impl StaticSymbol { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{eval, StaticSymbol}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -2914,9 +2987,10 @@ impl StaticSymbol { /// assert!(StaticSymbol::check("example").is_some()); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -2929,11 +3003,15 @@ impl StaticSymbol { /// # Examples /// /// ``` - /// use magnus::StaticSymbol; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let sym = StaticSymbol::new("example"); - /// assert_eq!(sym.name().unwrap(), "example"); + /// 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> { @@ -3076,16 +3154,9 @@ impl Ruby { } } -/// The internal value of a Ruby symbol. -#[cfg(feature = "deprecated-send-sync-value")] -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -#[repr(transparent)] -pub struct Id(ID); - /// The internal value of a Ruby symbol. /// /// See [`Ruby`](Ruby#id) for methods to create an `Id`. -#[cfg(not(feature = "deprecated-send-sync-value"))] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[repr(transparent)] pub struct Id(ID, PhantomData<*mut u8>); @@ -3101,6 +3172,7 @@ impl Id { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::value::Id; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -3108,9 +3180,10 @@ impl Id { /// assert_eq!(id.name().unwrap(), "example"); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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, @@ -3118,12 +3191,6 @@ impl Id { get_ruby!().intern(name.as_ref()) } - #[cfg(feature = "deprecated-send-sync-value")] - pub(crate) fn from_rb_id(id: ID) -> Self { - Self(id) - } - - #[cfg(not(feature = "deprecated-send-sync-value"))] #[inline] pub(crate) fn from_rb_id(id: ID) -> Self { Self(id, PhantomData) @@ -3144,6 +3211,7 @@ impl Id { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{value::Id, StaticSymbol}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -3152,9 +3220,10 @@ impl Id { /// assert!(Id::check("example").is_some()); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) @@ -3168,11 +3237,15 @@ impl Id { /// # Examples /// /// ``` - /// use magnus::value::Id; - /// # let _cleanup = unsafe { magnus::embed::init() }; + /// use magnus::{Error, Ruby}; /// - /// let id = Id::new("example"); - /// assert_eq!(id.name().unwrap(), "example"); + /// 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 { @@ -3203,9 +3276,10 @@ pub trait IntoId: Sized { /// Panics if called from a non-Ruby thread. See [`IntoId::into_id_with`] /// for the non-panicking version. #[cfg_attr( - not(feature = "friendly-api"), + 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!()) @@ -3427,12 +3501,16 @@ impl LazyId { /// # Examples /// /// ``` - /// use magnus::{rb_assert, value::LazyId}; + /// use magnus::{rb_assert, value::LazyId, Error, Ruby}; /// /// static EXAMPLE: LazyId = LazyId::new("example"); /// - /// # let _cleanup = unsafe { magnus::embed::init() }; - /// rb_assert!("val == :example", val = *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 { @@ -3481,16 +3559,16 @@ impl LazyId { /// # Examples /// /// ``` - /// use magnus::{ - /// value::{Id, LazyId}, - /// Ruby, - /// }; + /// use magnus::{value::LazyId, Error, Ruby}; /// /// static EXAMPLE: LazyId = LazyId::new("example"); /// - /// # let _cleanup = unsafe { magnus::embed::init() }; - /// let ruby = Ruby::get().unwrap(); - /// assert!(Id::new("example") == LazyId::get_inner_with(&EXAMPLE, &ruby)); + /// 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 { @@ -3515,16 +3593,20 @@ impl LazyId { /// # Examples /// /// ``` - /// use magnus::{rb_assert, value::LazyId}; + /// use magnus::{rb_assert, value::LazyId, Error, Ruby}; /// /// static EXAMPLE: LazyId = LazyId::new("example"); /// - /// # let _cleanup = unsafe { magnus::embed::init() }; - /// assert!(LazyId::try_get_inner(&EXAMPLE).is_none()); + /// fn example(ruby: &Ruby) -> Result<(), Error> { + /// assert!(LazyId::try_get_inner(&EXAMPLE).is_none()); /// - /// rb_assert!("val == :example", val = *EXAMPLE); + /// rb_assert!(ruby, "val == :example", val = *EXAMPLE); /// - /// assert!(LazyId::try_get_inner(&EXAMPLE).is_some()); + /// 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) } diff --git a/src/value/flonum.rs b/src/value/flonum.rs index 4e2f4373..407674fe 100644 --- a/src/value/flonum.rs +++ b/src/value/flonum.rs @@ -112,6 +112,7 @@ impl Flonum { /// # Examples /// /// ``` + /// # #![allow(deprecated)] /// use magnus::{rb_assert, Flonum}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// @@ -122,9 +123,10 @@ impl Flonum { /// assert!(Flonum::from_f64(1.7272337110188890e-77).is_err()); /// ``` #[cfg_attr( - not(feature = "friendly-api"), + 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) diff --git a/test b/test index 763634db..4fdee61b 100755 --- a/test +++ b/test @@ -1,6 +1,6 @@ #!/bin/sh -RUBY_VERSIONS="2.7.7 3.0.5 3.1.3 3.2.0" +RUBY_VERSIONS="2.7.8 3.0.6 3.1.4 3.2.2 3.3.0" ERRORS="" for VERSION in $RUBY_VERSIONS; do diff --git a/tests/block_call.rs b/tests/block_call.rs index c07d907b..c9ae4c83 100644 --- a/tests/block_call.rs +++ b/tests/block_call.rs @@ -10,7 +10,7 @@ fn it_can_call_method_with_block() { ruby.into_value(3_i64), ]); let _: Value = ary - .block_call("map!", (), |args, _| { + .block_call("map!", (), |_, args, _| { i64::try_convert(args[0]).map(|i| i * 4) }) .unwrap(); diff --git a/tests/bytes.rs b/tests/bytes.rs index 3eb962ae..df097541 100644 --- a/tests/bytes.rs +++ b/tests/bytes.rs @@ -1,10 +1,9 @@ #[test] -#[cfg(feature = "bytes")] fn it_converts_to_bytes() { - use magnus::{eval, RString}; + use magnus::RString; let ruby = unsafe { magnus::embed::init() }; - let s: RString = unsafe { ruby.eval("[0,0,0].pack('c*')").unwrap() }; + let s: RString = ruby.eval("[0,0,0].pack('c*')").unwrap(); assert_eq!(bytes::Bytes::from_static(&[0, 0, 0]), s.to_bytes()); } diff --git a/tests/debug_exception.rs b/tests/debug_exception.rs index 16ef10d6..f0908344 100644 --- a/tests/debug_exception.rs +++ b/tests/debug_exception.rs @@ -33,14 +33,16 @@ fn it_includes_backtrace_in_debug() { _ => panic!(), }; - assert_eq!( - r#"RuntimeError: bang -eval:3:in `foo' -eval:7:in `bar' -eval:11:in `baz' -eval:15:in `qux' -eval:18:in `
' -"#, - format!("{:#?}", ex) - ); + 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("
")); } diff --git a/tests/enumerator.rs b/tests/enumerator.rs index 797bc034..3374df12 100644 --- a/tests/enumerator.rs +++ b/tests/enumerator.rs @@ -4,7 +4,7 @@ use magnus::{prelude::*, RArray}; fn enumerator_impls_iterator() { let ruby = unsafe { magnus::embed::init() }; let a: RArray = ruby.eval("[1,2,3]").unwrap(); - let mut e = a.each(); + 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); diff --git a/tests/integer_traits.rs b/tests/integer_traits.rs new file mode 100644 index 00000000..d9f52f05 --- /dev/null +++ b/tests/integer_traits.rs @@ -0,0 +1,223 @@ +use 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(()) +} diff --git a/tests/proc_new.rs b/tests/proc_new.rs index dbe89ef1..a70522c5 100644 --- a/tests/proc_new.rs +++ b/tests/proc_new.rs @@ -1,9 +1,9 @@ -use magnus::{block::Proc, eval, gc, value::Opaque, Ruby, Value}; +use 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 |_args, _block| Ok((x.clone(), y))) + ruby.proc_from_fn(move |_ruby, _args, _block| Ok((x.clone(), y))) } #[test] @@ -14,7 +14,7 @@ fn proc_from_closure_can_be_called_later() { eval::(r#"1024.times.map {|i| "test#{i}"}"#).unwrap(); - gc::start(); + ruby.gc_start(); let res: bool = eval!(ruby, r#"proc.call == ["foo", "bar"]"#, proc).unwrap(); assert!(res); diff --git a/tests/range.rs b/tests/range.rs index 249e0c76..5e8e7654 100644 --- a/tests/range.rs +++ b/tests/range.rs @@ -5,10 +5,7 @@ fn it_converts_ranges() { 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..); - #[cfg(ruby_gte_2_7)] - { - magnus::rb_assert!(ruby, "range == (...7)", range = ..7); - magnus::rb_assert!(ruby, "range == (..7)", range = ..=7); - } + 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 = ..); } diff --git a/tests/return_custom_error.rs b/tests/return_custom_error.rs new file mode 100644 index 00000000..1d330e99 --- /dev/null +++ b/tests/return_custom_error.rs @@ -0,0 +1,28 @@ +use 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""# + ); +} diff --git a/tests/typed_data_subclass_from_ruby.rs b/tests/typed_data_subclass_from_ruby.rs index 46f87f17..884c9fd5 100644 --- a/tests/typed_data_subclass_from_ruby.rs +++ b/tests/typed_data_subclass_from_ruby.rs @@ -1,6 +1,6 @@ use magnus::{ - embed::init, eval, exception, function, method, prelude::*, rb_assert, typed_data::Obj, Error, - RClass, Value, + embed::init, eval, function, method, prelude::*, rb_assert, typed_data::Obj, Error, RClass, + Ruby, Value, }; const FACTOR: f64 = 1000000.0; @@ -12,16 +12,17 @@ struct Temperature { impl Temperature { fn new(class: RClass, k: f64) -> Result, Error> { + let ruby = Ruby::get_with(class); if k < 0.0 { return Err(Error::new( - exception::arg_error(), + ruby.exception_arg_error(), "temperature must be above absolute zero", )); } let value = Self { microkelvin: (k * FACTOR) as u64, }; - Ok(Obj::wrap_as(value, class)) + Ok(ruby.obj_wrap_as(value, class)) } fn to_kelvin(&self) -> f64 {