//! Functions for interoperability with rb-sys. //! //! These functions are provided to interface with the lower-level Ruby //! bindings provided by [rb-sys](rb_sys). You may want to use rb-sys when: //! //! 1. Magnus does not provide access to a Ruby API because the API can not be //! made safe & ergonomic. //! 2. Magnus exposed the API in a way that does not work for your use case. //! 3. The API just hasn't been implemented yet. //! //! Even if you are not in a position to contribute code to Magnus, please //! [open an issue](https://github.com/matsadler/magnus/issues) outlining your //! use case and the APIs you need whenever you find yourself reaching for this //! module. //! //! # Stability //! //! Functions in this module are considered unstable. While there is no plan //! to alter or remove them, non-backwards compatible changes in this module //! will not necessarily be considered as SemVer major changes. //! //! # Safety //! //! The unsafe functions in this module are capable of producing values that //! break the saftey guarantees of almost every other function in Magnus. Use //! them with care. use std::panic::UnwindSafe; use rb_sys::{ID, VALUE}; use crate::{ error::{self, raise, Error}, value::{Id, ReprValue, Value}, }; /// Converts from a [`Value`] to a raw [`VALUE`]. pub trait AsRawValue { /// Convert [`magnus::Value`](Value) to [`rb_sys::VALUE`]. /// /// # Examples /// /// ``` /// use magnus::{ /// rb_sys::{protect, AsRawValue}, /// Error, Ruby, /// }; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let foo = ruby.str_new("foo"); /// let bar = ruby.str_new("bar"); /// /// protect(|| unsafe { rb_sys::rb_str_buf_append(foo.as_raw(), bar.as_raw()) })?; /// /// assert_eq!(foo.to_string()?, "foobar"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn as_raw(self) -> VALUE; } /// Converts from a raw [`VALUE`] to a [`Value`]. pub trait FromRawValue { /// Convert [`rb_sys::VALUE`] to [`magnus::Value`](Value). /// /// # Safety /// /// You must only supply a valid [`VALUE`] obtained from [rb-sys](rb_sys) /// to this function. Using a invalid [`Value`] produced from this /// function will void all saftey guarantees provided by Magnus. /// /// # Examples /// /// ``` /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// use magnus::{rb_sys::FromRawValue, Value}; /// /// let raw_value = unsafe { rb_sys::rb_str_new("foo".as_ptr() as *mut _, 3) }; /// /// assert_eq!(unsafe { Value::from_raw(raw_value) }.to_string(), "foo"); /// ``` unsafe fn from_raw(value: VALUE) -> Self; } impl AsRawValue for T where T: ReprValue, { fn as_raw(self) -> VALUE { self.as_rb_value() } } impl FromRawValue for Value { unsafe fn from_raw(val: VALUE) -> Value { Value::new(val) } } /// Trait to convert a [`Id`] to a raw [`ID`]. pub trait AsRawId { /// Convert [`magnus::value::Id`](Id) to [`rb_sys::ID`]. /// /// # Examples /// /// ``` /// use magnus::{ /// prelude::*, /// rb_sys::{AsRawId, FromRawId}, /// value::Id, /// Error, Ruby, Symbol, /// }; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let foo: Id = ruby.to_symbol("foo").into(); /// let raw = foo.as_raw(); /// let from_raw_val: Symbol = unsafe { Id::from_raw(raw) }.into(); /// /// assert_eq!(from_raw_val.inspect(), ":foo"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` fn as_raw(self) -> ID; } /// Trait to convert from a raw [`ID`] to an [`Id`]. pub trait FromRawId { /// Convert [`rb_sys::ID`] to [`magnus::value::Id`](Id). /// /// # Safety /// /// You must only supply a valid, non-zero [`ID`] obtained from /// [rb-sys](rb_sys) to this function. Using a invalid [`Id`] produced /// from this function will void all saftey guarantees provided by /// Magnus. /// /// # Examples /// /// ``` /// use magnus::{ /// prelude::*, /// rb_sys::{AsRawId, FromRawId}, /// value::Id, /// Error, Ruby, Symbol, /// }; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let foo: Id = ruby.to_symbol("foo").into(); /// let from_raw_val: Symbol = unsafe { Id::from_raw(foo.as_raw()) }.into(); /// /// assert_eq!(from_raw_val.inspect(), ":foo"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` unsafe fn from_raw(id: ID) -> Self; } impl AsRawId for Id { fn as_raw(self) -> ID { self.as_rb_id() } } impl FromRawId for Id { unsafe fn from_raw(id: ID) -> Id { Id::from_rb_id(id.into()) } } /// Calls the given closure, catching all cases of unwinding from Ruby /// returning them as an [`Error`]. /// /// The most common will be exceptions, but this will also catch `throw`, /// `break`, `next`, `return` from a block, etc. /// /// All functions exposed by Magnus that call Ruby in a way that may unwind /// already use this internally, this should only be required to wrap functions /// from [rb-sys](rb_sys). pub fn protect(func: F) -> Result where F: FnOnce() -> VALUE, { error::protect(|| Value::new(func())).map(|v| v.as_rb_value()) } /// Attempts to catch cases of Rust unwinding, converting to a fatal [`Error`]. /// /// This should not be used to catch and discard panics. /// /// This function can be used to ensure Rust panics do not cross over to Ruby. /// This will convert a panic to a Ruby fatal [`Error`] that can then be used /// to safely terminate Ruby. /// /// All functions exposed by Magnus that allow Ruby to call Rust code already /// use this internally, this should only be required to wrap /// functions/closures given directly to [rb-sys](rb_sys). pub fn catch_unwind(func: F) -> Result where F: FnOnce() -> T + UnwindSafe, { std::panic::catch_unwind(func).map_err(Error::from_panic) } /// Resumes an [`Error`] previously caught by [`protect`]. /// /// All functions exposed by Magnus where it is safe to resume an error use /// this internally to automatically convert returned errors to raised /// exceptions. This should only be required to in functions/closures given /// directly to [rb-sys](rb_sys). /// /// # Safety /// /// Beware this function does not return and breaks the normal assumption that /// Rust code does not unwind during normal behaviour. This can break /// invariants in code that assumes unwinding only happens during terminating /// panics. /// /// If possible, only call this at the very end of a function/closure that is /// directly called by Ruby, not other Rust code, and ensure all other values /// in scope have been dropped before calling this function. pub unsafe fn resume_error(e: Error) -> ! { raise(e) }