//! Types and functions for working with Ruby blocks and Procs. //! //! See also [`Ruby`](Ruby#blocks) for more block related methods. use std::{ fmt, mem::{forget, size_of}, os::raw::c_int, slice, }; use rb_sys::{ rb_block_given_p, rb_block_proc, rb_data_typed_object_wrap, rb_obj_is_proc, rb_proc_arity, rb_proc_call_kw, rb_proc_lambda_p, rb_proc_new, rb_yield, rb_yield_splat, rb_yield_values_kw, VALUE, }; use crate::{ data_type_builder, enumerator::Enumerator, error::{ensure, protect, Error}, gc, into_value::{kw_splat, ArgList, IntoValue, RArrayArgList}, method::{Block, BlockReturn}, object::Object, r_array::RArray, try_convert::TryConvert, typed_data::{DataType, DataTypeFunctions}, value::{ private::{self, ReprValue as _}, NonZeroValue, ReprValue, Value, }, Ruby, }; /// # `Proc` /// /// Functions that can be used to create instances of [`Proc`], Ruby's /// representation of a block as an object. /// /// See also the [`Proc`] type. impl Ruby { /// Create a new `Proc`. /// /// As `block` is a function pointer, only functions and closures that do /// not capture any variables are permitted. For more flexibility (at the /// cost of allocating) see [`proc_from_fn`](Ruby::proc_from_fn). /// /// # Examples /// /// ``` /// use magnus::{prelude::*, rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let proc = ruby.proc_new(|_ruby, args, _block| { /// let acc = i64::try_convert(*args.get(0).unwrap())?; /// let i = i64::try_convert(*args.get(1).unwrap())?; /// Ok(acc + i) /// }); /// /// rb_assert!(ruby, "proc.call(1, 2) == 3", proc); /// /// rb_assert!(ruby, "[1, 2, 3, 4, 5].inject(&proc) == 15", proc); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn proc_new(&self, block: fn(&Ruby, &[Value], Option) -> R) -> Proc where R: BlockReturn, { unsafe extern "C" fn call( _yielded_arg: VALUE, callback_arg: VALUE, argc: c_int, argv: *const VALUE, blockarg: VALUE, ) -> VALUE where R: BlockReturn, { let func = std::mem::transmute::) -> R>(callback_arg); func.call_handle_error(argc, argv as *const Value, Value::new(blockarg)) .as_rb_value() } let call_func = call:: as unsafe extern "C" fn(VALUE, VALUE, c_int, *const VALUE, VALUE) -> VALUE; unsafe { #[allow(clippy::fn_to_numeric_cast)] Proc::from_rb_value_unchecked(rb_proc_new(Some(call_func), block as VALUE)) } } /// Create a new `Proc`. /// /// See also [`proc_new`](Ruby::proc_new), which is more efficient when /// `block` is a function or closure that does not capture any variables. /// /// # Examples /// /// ``` /// use magnus::{prelude::*, rb_assert, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let mut count = 0; /// /// let proc = ruby.proc_from_fn(move |_ruby, args, _block| { /// let step = i64::try_convert(*args.get(0).unwrap())?; /// count += step; /// Ok(count) /// }); /// /// rb_assert!(ruby, "proc.call(1) == 1", proc); /// rb_assert!(ruby, "proc.call(1) == 2", proc); /// rb_assert!(ruby, "proc.call(2) == 4", proc); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn proc_from_fn(&self, block: F) -> Proc where F: 'static + Send + FnMut(&Ruby, &[Value], Option) -> R, R: BlockReturn, { unsafe extern "C" fn call( _yielded_arg: VALUE, callback_arg: VALUE, argc: c_int, argv: *const VALUE, blockarg: VALUE, ) -> VALUE where F: FnMut(&Ruby, &[Value], Option) -> R, R: BlockReturn, { let closure = &mut *(callback_arg as *mut F); closure .call_handle_error(argc, argv as *const Value, Value::new(blockarg)) .as_rb_value() } let (closure, keepalive) = wrap_closure(block); let call_func = call:: as unsafe extern "C" fn(VALUE, VALUE, c_int, *const VALUE, VALUE) -> VALUE; let proc = unsafe { Proc::from_rb_value_unchecked(rb_proc_new(Some(call_func), closure as VALUE)) }; // ivar without @ prefix is invisible from Ruby proc.ivar_set("__rust_closure", keepalive).unwrap(); proc } } /// Wrapper type for a Value known to be an instance of Ruby’s Proc class. /// /// See the [`ReprValue`] and [`Object`] traits for additional methods /// available on this type. See [`Ruby`](Ruby#proc) for methods to create a /// `Proc`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct Proc(NonZeroValue); impl Proc { /// Return `Some(Proc)` if `val` is a `Proc`, `None` otherwise. /// /// # Examples /// /// ``` /// use magnus::{block::Proc, eval, Value}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let val: Value = eval("Proc.new {|a, b| a + b}").unwrap(); /// assert!(Proc::from_value(val).is_some()); /// /// let val: Value = eval("1 + 2").unwrap(); /// assert!(Proc::from_value(val).is_none()); /// ``` #[inline] pub fn from_value(val: Value) -> Option { unsafe { Value::new(rb_obj_is_proc(val.as_rb_value())) .to_bool() .then(|| Self(NonZeroValue::new_unchecked(val))) } } #[inline] pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self { Self(NonZeroValue::new_unchecked(Value::new(val))) } /// Create a new `Proc`. /// /// As `block` is a function pointer, only functions and closures that do /// not capture any variables are permitted. For more flexibility (at the /// cost of allocating) see [`from_fn`](Proc::from_fn). /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::proc_new`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{block::Proc, prelude::*, rb_assert}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let proc = Proc::new(|_ruby, args, _block| { /// let acc = i64::try_convert(*args.get(0).unwrap())?; /// let i = i64::try_convert(*args.get(1).unwrap())?; /// Ok(acc + i) /// }); /// /// rb_assert!("proc.call(1, 2) == 3", proc); /// /// rb_assert!("[1, 2, 3, 4, 5].inject(&proc) == 15", proc); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::proc_new` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn new(block: fn(&Ruby, &[Value], Option) -> R) -> Self where R: BlockReturn, { get_ruby!().proc_new(block) } /// Create a new `Proc`. /// /// See also [`Proc::new`], which is more efficient when `block` is a /// function or closure that does not capture any variables. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::proc_from_fn`] for /// the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{block::Proc, prelude::*, rb_assert}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let mut count = 0; /// /// let proc = Proc::from_fn(move |_ruby, args, _block| { /// let step = i64::try_convert(*args.get(0).unwrap())?; /// count += step; /// Ok(count) /// }); /// /// rb_assert!("proc.call(1) == 1", proc); /// rb_assert!("proc.call(1) == 2", proc); /// rb_assert!("proc.call(2) == 4", proc); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::proc_from_fn` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn from_fn(block: F) -> Self where F: 'static + Send + FnMut(&Ruby, &[Value], Option) -> R, R: BlockReturn, { get_ruby!().proc_from_fn(block) } /// Call the proc with `args`. /// /// Returns `Ok(T)` if the proc runs without error and the return value /// converts into a `T`, or returns `Err` if the proc raises or the /// conversion fails. /// /// # Examples /// /// ``` /// use magnus::{block::Proc, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let proc: Proc = ruby.eval("Proc.new {|a, b| a + b}").unwrap(); /// /// // call with a tuple /// let result: i64 = proc.call((1, 2)).unwrap(); /// assert_eq!(3, result); /// /// // call with a slice /// let result: i64 = proc /// .call(&[ruby.integer_from_i64(3), ruby.integer_from_i64(4)][..]) /// .unwrap(); /// assert_eq!(7, result); /// /// // call with an array /// let result: i64 = proc /// .call([ruby.integer_from_i64(5), ruby.integer_from_i64(6)]) /// .unwrap(); /// assert_eq!(11, result); /// /// // call with a Ruby array /// let array = ruby.ary_from_vec(vec![7, 8]); /// let result: i64 = proc.call(array).unwrap(); /// assert_eq!(15, result); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// With keyword arguments: /// /// ``` /// use magnus::{block::Proc, kwargs, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let proc: Proc = ruby.eval("Proc.new {|a, b:, c:| a + b + c}").unwrap(); /// /// let result: i64 = proc.call((1, kwargs!("b" => 2, "c" => 3))).unwrap(); /// assert_eq!(6, result); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` /// /// Ignoring return value: /// /// ``` /// use magnus::{block::Proc, rb_assert, Error, Ruby, Value}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let proc: Proc = ruby.eval("Proc.new { $called = true }").unwrap(); /// /// let _: Value = proc.call(()).unwrap(); /// /// rb_assert!(ruby, "$called == true"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn call(self, args: A) -> Result where A: RArrayArgList, T: TryConvert, { let kw_splat = kw_splat(&args); let args = args.into_array_arg_list_with(&Ruby::get_with(self)); unsafe { protect(|| { Value::new(rb_proc_call_kw( self.as_rb_value(), args.as_rb_value(), kw_splat as c_int, )) }) .and_then(TryConvert::try_convert) } } /// Returns the number of arguments `self` takes. /// /// If `self` takes no arguments, returns `0`. /// If `self` takes only required arguments, returns the number of required /// arguments. /// If `self` is a lambda and has optional arguments, or is not a lambda /// and takes a splat argument, returns `-n-1`, where `n` is the number of /// required arguments. /// If `self` is not a lambda, and takes a finite number of optional /// arguments, returns the number of required arguments. /// Keyword arguments are considered as a single additional argument, that /// argument being required if any keyword argument is required. /// /// # Examples /// /// ``` /// use magnus::{block::Proc, eval}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let proc: Proc = eval("proc {nil}").unwrap(); /// assert_eq!(proc.arity(), 0); /// /// let proc: Proc = eval("proc {|a| a + 1}").unwrap(); /// assert_eq!(proc.arity(), 1); /// /// let proc: Proc = eval("proc {|a, b| a + b}").unwrap(); /// assert_eq!(proc.arity(), 2); /// /// let proc: Proc = eval("proc {|*args| args.sum}").unwrap(); /// assert_eq!(proc.arity(), -1); /// ``` pub fn arity(self) -> i64 { unsafe { rb_proc_arity(self.as_rb_value()) as i64 } } /// Returns whether or not `self` is a lambda. /// /// # Examples /// /// ``` /// use magnus::{block::Proc, eval}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let proc: Proc = eval("proc {|a, b| a + b}").unwrap(); /// assert!(!proc.is_lambda()); /// /// let proc: Proc = eval("lambda {|a, b| a + b}").unwrap(); /// assert!(proc.is_lambda()); /// ``` pub fn is_lambda(self) -> bool { unsafe { Value::new(rb_proc_lambda_p(self.as_rb_value())).to_bool() } } } impl fmt::Display for Proc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", unsafe { self.to_s_infallible() }) } } impl fmt::Debug for Proc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inspect()) } } impl IntoValue for Proc { #[inline] fn into_value_with(self, _: &Ruby) -> Value { self.0.get() } } impl Object for Proc {} unsafe impl private::ReprValue for Proc {} impl ReprValue for Proc {} impl TryConvert for Proc { fn try_convert(val: Value) -> Result { let handle = Ruby::get_with(val); if let Some(p) = Proc::from_value(val) { return Ok(p); } let p_val: Value = match val.funcall("to_proc", ()) { Ok(v) => v, Err(_) => { return Err(Error::new( handle.exception_type_error(), format!("no implicit conversion of {} into Proc", unsafe { val.classname() },), )) } }; Proc::from_value(val).ok_or_else(|| { Error::new( handle.exception_type_error(), format!( "can't convert {0} to Proc ({0}#to_proc gives {1})", unsafe { val.classname() }, unsafe { p_val.classname() }, ), ) }) } } /// Wrap a closure in a Ruby object with no class. /// /// This effectivly makes the closure's lifetime managed by Ruby. It will be /// dropped when the returned `Value` is garbage collected. fn wrap_closure(func: F) -> (*mut F, Value) where F: FnMut(&Ruby, &[Value], Option) -> R, R: BlockReturn, { struct Closure(F, DataType); unsafe impl Send for Closure {} impl DataTypeFunctions for Closure { fn mark(&self, marker: &gc::Marker) { // Attempt to mark any Ruby values captured in a closure. // Rust's closures are structs that contain all the values they // have captured. This reads that struct as a slice of VALUEs and // calls rb_gc_mark_locations which calls gc_mark_maybe which // marks VALUEs and ignores non-VALUEs marker.mark_slice(unsafe { slice::from_raw_parts( &self.0 as *const _ as *const Value, size_of::() / size_of::(), ) }); } } let data_type = data_type_builder!(Closure, "rust closure") .free_immediately() .mark() .build(); let boxed = Box::new(Closure(func, data_type)); let ptr = Box::into_raw(boxed); let value = unsafe { Value::new(rb_data_typed_object_wrap( 0, // using 0 for the class will hide the object from ObjectSpace ptr as *mut _, (*ptr).1.as_rb_data_type() as *const _, )) }; unsafe { (&mut (*ptr).0 as *mut F, value) } } /// # Blocks /// /// Functions to enable working with Ruby blocks. /// /// See also the [`block`](self) module. impl Ruby { /// Returns whether a Ruby block has been supplied to the current method. /// /// # Examples /// /// ``` /// use magnus::{function, rb_assert, Error, Ruby}; /// /// fn got_block(ruby: &Ruby) -> bool { /// ruby.block_given() /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_function("got_block?", function!(got_block, 0)); /// /// rb_assert!(ruby, "got_block? {} == true"); /// rb_assert!(ruby, "got_block? == false"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn block_given(&self) -> bool { unsafe { rb_block_given_p() != 0 } } /// Returns the block given to the current method as a [`Proc`] instance. /// /// # Examples /// /// ``` /// use magnus::{block::Proc, function, rb_assert, Error, Ruby}; /// /// fn make_proc(ruby: &Ruby) -> Result { /// ruby.block_proc() /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_function("make_proc", function!(make_proc, 0)); /// /// rb_assert!(ruby, "make_proc {}.is_a?(Proc)"); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn block_proc(&self) -> Result { let val = unsafe { protect(|| Value::new(rb_block_proc()))? }; Ok(Proc::from_value(val).unwrap()) } /// Yields a value to the block given to the current method. /// /// **Note:** A method using `yield_value` converted to an Enumerator with /// `to_enum`/[`Value::enumeratorize`] will result in a non-functional /// Enumerator on versions of Ruby before 3.1. See [`Yield`] for an /// alternative. /// /// # Examples /// /// ``` /// use magnus::{function, rb_assert, Error, Ruby, Value}; /// /// fn metasyntactic_variables(ruby: &Ruby) -> Result<(), Error> { /// let _: Value = ruby.yield_value("foo")?; /// let _: Value = ruby.yield_value("bar")?; /// let _: Value = ruby.yield_value("baz")?; /// Ok(()) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_function( /// "metasyntactic_variables", /// function!(metasyntactic_variables, 0), /// ); /// /// let vars = ruby.ary_new(); /// rb_assert!( /// ruby, /// "metasyntactic_variables {|var| vars << var} == nil", /// vars /// ); /// rb_assert!(ruby, r#"vars == ["foo", "bar", "baz"]"#, vars); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn yield_value(&self, val: T) -> Result where T: IntoValue, U: TryConvert, { let val = self.into_value(val); unsafe { protect(|| Value::new(rb_yield(val.as_rb_value()))).and_then(TryConvert::try_convert) } } /// Yields multiple values to the block given to the current method. /// /// **Note:** A method using `yield_values` converted to an Enumerator with /// `to_enum`/[`Value::enumeratorize`] will result in a non-functional /// Enumerator on versions of Ruby before 3.1. See [`YieldValues`] for an /// alternative. /// /// # Examples /// /// ``` /// use magnus::{function, kwargs, rb_assert, Error, Ruby, Value}; /// /// fn metasyntactic_variables(ruby: &Ruby) -> Result<(), Error> { /// let _: Value = ruby.yield_values((0, kwargs!("var" => "foo")))?; /// let _: Value = ruby.yield_values((1, kwargs!("var" => "bar")))?; /// let _: Value = ruby.yield_values((2, kwargs!("var" => "baz")))?; /// Ok(()) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_function( /// "metasyntactic_variables", /// function!(metasyntactic_variables, 0), /// ); /// /// let vars = ruby.ary_new(); /// rb_assert!( /// ruby, /// "metasyntactic_variables {|pos, var:| vars << [pos, var]} == nil", /// vars /// ); /// rb_assert!( /// ruby, /// r#"vars == [[0, "foo"], [1, "bar"], [2, "baz"]]"#, /// vars /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn yield_values(&self, vals: T) -> Result where T: ArgList, U: TryConvert, { let kw_splat = kw_splat(&vals); let vals = vals.into_arg_list_with(self); let slice = vals.as_ref(); unsafe { protect(|| { Value::new(rb_yield_values_kw( slice.len() as c_int, slice.as_ptr() as *const VALUE, kw_splat as c_int, )) }) .and_then(TryConvert::try_convert) } } /// Yields a Ruby Array to the block given to the current method. /// /// **Note:** A method using `yield_splat` converted to an Enumerator with /// `to_enum`/[`Value::enumeratorize`] will result in a non-functional /// Enumerator on versions of Ruby before 3.1. See [`YieldSplat`] for an /// alternative. /// /// # Examples /// /// ``` /// use magnus::{function, rb_assert, Error, Ruby, Value}; /// /// fn metasyntactic_variables(ruby: &Ruby) -> Result<(), Error> { /// let ary = ruby.ary_new(); /// ary.push(0)?; /// ary.push("foo")?; /// let _: Value = ruby.yield_splat(ary)?; /// let ary = ruby.ary_new(); /// ary.push(1)?; /// ary.push("bar")?; /// let _: Value = ruby.yield_splat(ary)?; /// let ary = ruby.ary_new(); /// ary.push(2)?; /// ary.push("baz")?; /// let _: Value = ruby.yield_splat(ary)?; /// Ok(()) /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_function( /// "metasyntactic_variables", /// function!(metasyntactic_variables, 0), /// ); /// /// let vars = ruby.ary_new(); /// rb_assert!( /// ruby, /// "metasyntactic_variables {|pos, var| vars << [pos, var]} == nil", /// vars /// ); /// rb_assert!( /// ruby, /// r#"vars == [[0, "foo"], [1, "bar"], [2, "baz"]]"#, /// vars /// ); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn yield_splat(&self, vals: RArray) -> Result where T: TryConvert, { unsafe { protect(|| Value::new(rb_yield_splat(vals.as_rb_value()))) .and_then(TryConvert::try_convert) } } } /// Returns whether a Ruby block has been supplied to the current method. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::block_given`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{block::block_given, define_global_function, function, rb_assert}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// fn got_block() -> bool { /// block_given() /// } /// /// define_global_function("got_block?", function!(got_block, 0)); /// /// rb_assert!("got_block? {} == true"); /// rb_assert!("got_block? == false"); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::block_given` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn block_given() -> bool { get_ruby!().block_given() } /// Returns the block given to the current method as a [`Proc`] instance. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::block_proc`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{ /// block::{block_proc, Proc}, /// define_global_function, function, rb_assert, Error, /// }; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// fn make_proc() -> Result { /// block_proc() /// } /// /// define_global_function("make_proc", function!(make_proc, 0)); /// /// rb_assert!("make_proc {}.is_a?(Proc)"); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::block_proc` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn block_proc() -> Result { get_ruby!().block_proc() } /// Yields a value to the block given to the current method. /// /// **Note:** A method using `yield_value` converted to an Enumerator with /// `to_enum`/[`Value::enumeratorize`] will result in a non-functional /// Enumerator on versions of Ruby before 3.1. See [`Yield`] for an /// alternative. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::yield_value`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{ /// block::yield_value, define_global_function, function, rb_assert, Error, RArray, Value, /// }; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// fn metasyntactic_variables() -> Result<(), Error> { /// let _: Value = yield_value("foo")?; /// let _: Value = yield_value("bar")?; /// let _: Value = yield_value("baz")?; /// Ok(()) /// } /// /// define_global_function( /// "metasyntactic_variables", /// function!(metasyntactic_variables, 0), /// ); /// /// let vars = RArray::new(); /// rb_assert!("metasyntactic_variables {|var| vars << var} == nil", vars); /// rb_assert!(r#"vars == ["foo", "bar", "baz"]"#, vars); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::yield_value` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn yield_value(val: T) -> Result where T: IntoValue, U: TryConvert, { get_ruby!().yield_value(val) } /// Yields multiple values to the block given to the current method. /// /// **Note:** A method using `yield_values` converted to an Enumerator with /// `to_enum`/[`Value::enumeratorize`] will result in a non-functional /// Enumerator on versions of Ruby before 3.1. See [`YieldValues`] for an /// alternative. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::yield_values`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{ /// block::yield_values, define_global_function, function, rb_assert, Error, RArray, Value, /// }; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// fn metasyntactic_variables() -> Result<(), Error> { /// let _: Value = yield_values((0, "foo"))?; /// let _: Value = yield_values((1, "bar"))?; /// let _: Value = yield_values((2, "baz"))?; /// Ok(()) /// } /// /// define_global_function( /// "metasyntactic_variables", /// function!(metasyntactic_variables, 0), /// ); /// /// let vars = RArray::new(); /// rb_assert!( /// "metasyntactic_variables {|pos, var| vars << [pos, var]} == nil", /// vars /// ); /// rb_assert!(r#"vars == [[0, "foo"], [1, "bar"], [2, "baz"]]"#, vars); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::yield_values` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn yield_values(vals: T) -> Result where T: ArgList, U: TryConvert, { get_ruby!().yield_values(vals) } /// Yields a Ruby Array to the block given to the current method. /// /// **Note:** A method using `yield_splat` converted to an Enumerator with /// `to_enum`/[`Value::enumeratorize`] will result in a non-functional /// Enumerator on versions of Ruby before 3.1. See [`YieldSplat`] for an /// alternative. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::yield_splat`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{ /// block::yield_splat, define_global_function, function, rb_assert, Error, RArray, Value, /// }; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// fn metasyntactic_variables() -> Result<(), Error> { /// let ary = RArray::new(); /// ary.push(0)?; /// ary.push("foo")?; /// let _: Value = yield_splat(ary)?; /// let ary = RArray::new(); /// ary.push(1)?; /// ary.push("bar")?; /// let _: Value = yield_splat(ary)?; /// let ary = RArray::new(); /// ary.push(2)?; /// ary.push("baz")?; /// let _: Value = yield_splat(ary)?; /// Ok(()) /// } /// /// define_global_function( /// "metasyntactic_variables", /// function!(metasyntactic_variables, 0), /// ); /// /// let vars = RArray::new(); /// rb_assert!( /// "metasyntactic_variables {|pos, var| vars << [pos, var]} == nil", /// vars /// ); /// rb_assert!(r#"vars == [[0, "foo"], [1, "bar"], [2, "baz"]]"#, vars); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::yield_splat` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn yield_splat(vals: RArray) -> Result where T: TryConvert, { get_ruby!().yield_splat(vals) } // Our regular implementation of `yield` breaks yielding methods being // converted to Enumerators because of the protect call not being compatible // with the fibers used in Ruby itself to implement `Enumerator#next`. // We have to use protect in `yield` because otherwise Ruby code can // `break`/`return` through Rust code and break Rust invariants. // This gives up using `protect` by instead using `ensure`, not exposing the // `yield` call to user code, and maintaining the invariants ourselves. As it // can still be `brake`/`return`ed though it can't be public as it's only safe // to call as the last thing in one of our method wrappers (where the raise // would normally go). Returning an iterator from a method will trigger this. pub(crate) unsafe fn do_yield_iter(mut iter: I) where I: Iterator, T: IntoValue, { let handle = Ruby::get_unchecked(); let ptr = &mut iter as *mut I; forget(iter); // we're going to drop this ourself; // ensure runs the first closure, but yield may raise, so the first // closure might never reach the end, so wouldn't drop. The second // closure is always run, and always after the first, so we do the // drop there ensure( || { for val in &mut *ptr { rb_yield(handle.into_value(val).as_rb_value()); } handle.qnil() }, || { ptr.drop_in_place(); }, ); } // see do_yield_iter pub(crate) unsafe fn do_yield_values_iter(mut iter: I) where I: Iterator, T: ArgList, { let handle = Ruby::get_unchecked(); let ptr = &mut iter as *mut I; forget(iter); ensure( || { for val in &mut *ptr { let kw_splat = kw_splat(&val); let vals = val.into_arg_list_with(&handle); let slice = vals.as_ref(); rb_yield_values_kw( slice.len() as c_int, slice.as_ptr() as *const VALUE, kw_splat as c_int, ); } handle.qnil() }, || { ptr.drop_in_place(); }, ); } // see do_yield_iter pub(crate) unsafe fn do_yield_splat_iter(mut iter: I) where I: Iterator, { let ptr = &mut iter as *mut I; forget(iter); ensure( || { for val in &mut *ptr { rb_yield_splat(val.as_rb_value()); } Ruby::get_unchecked().qnil() }, || { ptr.drop_in_place(); }, ); } /// Helper type for functions that either yield a single value to a block or /// return an Enumerator. /// /// `I` must implement `Iterator`, where `T` implements [`IntoValue`]. /// /// # Examples /// /// ``` /// use magnus::{block::Yield, method, prelude::*, rb_assert, Error, Ruby, Value}; /// /// fn count_to_3(ruby: &Ruby, rb_self: Value) -> Yield> { /// if ruby.block_given() { /// Yield::Iter(1..=3) /// } else { /// Yield::Enumerator(rb_self.enumeratorize("count_to_3", ())) /// } /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_function("count_to_3", method!(count_to_3, 0)); /// /// // call Ruby method with a block. /// let a = ruby.ary_new(); /// rb_assert!(ruby, "count_to_3 {|i| a << i} == nil", a); /// rb_assert!(ruby, "a == [1, 2, 3]", a); /// /// // call Ruby method without a block. /// let enumerator: Value = ruby.eval("count_to_3").unwrap(); /// /// rb_assert!(ruby, "enumerator.next == 1", enumerator); /// rb_assert!(ruby, "enumerator.next == 2", enumerator); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub enum Yield { /// Yields `I::Item` to given block. Iter(I), /// Returns `Enumerator` from the method. Enumerator(Enumerator), } /// Helper type for functions that either yield multiple values to a block or /// return an Enumerator. /// /// `I` must implement `Iterator`, where `T` implements [`ArgList`]. /// /// # Examples /// /// ``` /// use magnus::{block::YieldValues, method, prelude::*, rb_assert, Error, Ruby, Value}; /// /// fn count_to_3_abc( /// ruby: &Ruby, /// rb_self: Value, /// ) -> YieldValues> { /// if ruby.block_given() { /// YieldValues::Iter((1..=3).zip('a'..='c')) /// } else { /// YieldValues::Enumerator(rb_self.enumeratorize("count_to_3_abc", ())) /// } /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_function("count_to_3_abc", method!(count_to_3_abc, 0)); /// /// // call Ruby method with a block. /// let a = ruby.ary_new(); /// rb_assert!(ruby, "count_to_3_abc {|i, c| a << [i, c]} == nil", a); /// rb_assert!(ruby, r#"a == [[1, "a"], [2, "b"], [3, "c"]]"#, a); /// /// // call Ruby method without a block. /// let enumerator: Value = ruby.eval("count_to_3_abc").unwrap(); /// /// rb_assert!(ruby, r#"enumerator.next == [1, "a"]"#, enumerator); /// rb_assert!(ruby, r#"enumerator.next == [2, "b"]"#, enumerator); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub enum YieldValues { /// Yields `I::Item` to given block. Iter(I), /// Returns `Enumerator` from the method. Enumerator(Enumerator), } /// Helper type for functions that either yield an array to a block or /// return an Enumerator. /// /// `I` must implement `Iterator`. /// /// # Examples /// /// ``` /// use magnus::{block::YieldSplat, method, prelude::*, rb_assert, Error, RArray, Ruby, Value}; /// /// fn count_to_3_abc(ruby: &Ruby, rb_self: Value) -> YieldSplat> { /// if ruby.block_given() { /// YieldSplat::Iter((1..=3).zip('a'..='c').map(|(i, c)| { /// // we know this will be called on a Ruby thread so it's safe /// // to get a handle to Ruby, but we don't want to be tied to the /// // lifetime of the existing `ruby`. /// let ary = unsafe { Ruby::get_unchecked() }.ary_new(); /// ary.push(i).unwrap(); /// ary.push(c).unwrap(); /// ary /// })) /// } else { /// YieldSplat::Enumerator(rb_self.enumeratorize("count_to_3_abc", ())) /// } /// } /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.define_global_function("count_to_3_abc", method!(count_to_3_abc, 0)); /// /// // call Ruby method with a block. /// let a = ruby.ary_new(); /// rb_assert!(ruby, "count_to_3_abc {|i, c| a << [i, c]} == nil", a); /// rb_assert!(ruby, r#"a == [[1, "a"], [2, "b"], [3, "c"]]"#, a); /// /// // call Ruby method without a block. /// let enumerator: Value = ruby.eval("count_to_3_abc").unwrap(); /// /// rb_assert!(ruby, r#"enumerator.next == [1, "a"]"#, enumerator); /// rb_assert!(ruby, r#"enumerator.next == [2, "b"]"#, enumerator); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub enum YieldSplat { /// Yields `I::Item` to given block. Iter(I), /// Returns `Enumerator` from the method. Enumerator(Enumerator), }