Skip to main content

rhai/types/
fn_ptr.rs

1//! The `FnPtr` type.
2
3use crate::func::FnCallArgs;
4use crate::tokenizer::{is_reserved_keyword_or_symbol, is_valid_function_name, Token};
5use crate::types::dynamic::Variant;
6use crate::{
7    expose_under_internals, Dynamic, Engine, FnArgsVec, FuncArgs, ImmutableString,
8    NativeCallContext, Position, RhaiError, RhaiResult, RhaiResultOf, Shared, StaticVec, ThinVec,
9    AST, ERR, PERR,
10};
11#[cfg(feature = "no_std")]
12use std::prelude::v1::*;
13use std::{
14    any::type_name,
15    convert::{TryFrom, TryInto},
16    fmt, mem,
17    ops::{Index, IndexMut},
18};
19
20/// Function pointer type.
21#[derive(Clone, Default)]
22pub enum FnPtrType {
23    /// Normal function pointer.
24    #[default]
25    Normal,
26    /// Linked to a script-defined function.
27    #[cfg(not(feature = "no_function"))]
28    Script(Shared<crate::ast::ScriptFuncDef>),
29    /// Embedded native Rust function.
30    #[cfg(not(feature = "sync"))]
31    Native(Shared<dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult + 'static>),
32    #[cfg(feature = "sync")]
33    Native(
34        Shared<dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult + Send + Sync + 'static>,
35    ),
36}
37
38impl fmt::Display for FnPtrType {
39    #[cold]
40    #[inline(never)]
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        match self {
43            Self::Normal => f.write_str("Fn"),
44            #[cfg(not(feature = "no_function"))]
45            Self::Script(..) => f.write_str("Fn*"),
46            Self::Native(..) => f.write_str("Fn"),
47        }
48    }
49}
50
51/// A general function pointer, which may carry additional (i.e. curried) argument values
52/// to be passed onto a function during a call.
53#[derive(Clone)]
54pub struct FnPtr {
55    /// Name of the function.
56    pub(crate) name: ImmutableString,
57    /// Curried arguments.
58    pub(crate) curry: ThinVec<Dynamic>,
59    /// Encapsulated environment.
60    #[cfg(not(feature = "no_function"))]
61    pub(crate) env: Option<Shared<crate::ast::EncapsulatedEnviron>>,
62    /// Type of function pointer.
63    pub(crate) typ: FnPtrType,
64}
65
66impl fmt::Display for FnPtr {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        write!(f, "Fn({})", self.fn_name())
69    }
70}
71
72impl fmt::Debug for FnPtr {
73    #[cold]
74    #[inline(never)]
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        let name = match self {
77            #[cfg(not(feature = "no_function"))]
78            _ if self.env.is_some() => format!("{}+", self.typ),
79            _ => self.typ.to_string(),
80        };
81        let ff = &mut f.debug_tuple(&name);
82        ff.field(&self.name);
83        self.curry.iter().for_each(|curry| {
84            ff.field(curry);
85        });
86        ff.finish()?;
87
88        Ok(())
89    }
90}
91
92impl FnPtr {
93    /// Create a new function pointer.
94    ///
95    /// # Errors
96    ///
97    /// Returns an error if the function name is not a valid identifier or is a reserved keyword.
98    #[inline(always)]
99    pub fn new(name: impl Into<ImmutableString>) -> RhaiResultOf<Self> {
100        name.into().try_into()
101    }
102    /// Create a new function pointer from a native Rust function.
103    ///
104    /// # Errors
105    ///
106    /// Returns an error if the function name is not a valid identifier or is a reserved keyword.
107    ///
108    /// # WARNING - Unstable API
109    ///
110    /// This API is volatile and may change in the future.
111    ///
112    /// # Callback Function Signature
113    ///
114    /// `Fn(context: NativeCallContext, &mut [&mut Dynamic]) -> Result<Dynamic, Box<EvalAltResult>>`
115    #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."]
116    #[inline(always)]
117    pub fn from_fn(
118        name: impl Into<ImmutableString>,
119        #[cfg(not(feature = "sync"))] func: impl Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult
120            + 'static,
121        #[cfg(feature = "sync")] func: impl Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult
122            + Send
123            + Sync
124            + 'static,
125    ) -> RhaiResultOf<Self> {
126        #[allow(deprecated)]
127        Self::from_dyn_fn(name, Box::new(func))
128    }
129    /// Create a new function pointer from a native Rust function.
130    ///
131    /// # Errors
132    ///
133    /// Returns an error if the function name is not a valid identifier or is a reserved keyword.
134    ///
135    /// # WARNING - Unstable API
136    ///
137    /// This API is volatile and may change in the future.
138    ///
139    /// # Callback Function Signature
140    ///
141    /// `Fn(context: NativeCallContext, &mut [&mut Dynamic]) -> Result<Dynamic, Box<EvalAltResult>>`
142    #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."]
143    #[inline]
144    pub fn from_dyn_fn(
145        name: impl Into<ImmutableString>,
146        #[cfg(not(feature = "sync"))] func: Box<
147            dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult + 'static,
148        >,
149        #[cfg(feature = "sync")] func: Box<
150            dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult + Send + Sync + 'static,
151        >,
152    ) -> RhaiResultOf<Self> {
153        let mut fp = Self::new(name)?;
154        fp.typ = FnPtrType::Native(Shared::new(func));
155        Ok(fp)
156    }
157
158    /// Get the name of the function.
159    #[inline(always)]
160    #[must_use]
161    pub fn fn_name(&self) -> &str {
162        self.fn_name_raw()
163    }
164    /// Get the name of the function.
165    #[inline(always)]
166    #[must_use]
167    pub(crate) const fn fn_name_raw(&self) -> &ImmutableString {
168        &self.name
169    }
170    /// Get the curried arguments.
171    #[inline(always)]
172    pub fn curry(&self) -> &[Dynamic] {
173        self.curry.as_ref()
174    }
175    /// Iterate the curried arguments.
176    #[inline(always)]
177    pub fn iter_curry(&self) -> impl Iterator<Item = &Dynamic> {
178        self.curry.iter()
179    }
180    /// Mutably-iterate the curried arguments.
181    #[inline(always)]
182    pub fn iter_curry_mut(&mut self) -> impl Iterator<Item = &mut Dynamic> {
183        self.curry.iter_mut()
184    }
185    /// Add a new curried argument.
186    #[inline(always)]
187    pub fn add_curry(&mut self, value: Dynamic) -> &mut Self {
188        self.curry.push(value);
189        self
190    }
191    /// Set curried arguments to the function pointer.
192    #[inline]
193    pub fn set_curry(&mut self, values: impl IntoIterator<Item = Dynamic>) -> &mut Self {
194        self.curry = values.into_iter().collect();
195        self
196    }
197    /// Is the function pointer curried?
198    #[inline(always)]
199    #[must_use]
200    pub fn is_curried(&self) -> bool {
201        !self.curry.is_empty()
202    }
203    /// Does the function pointer refer to an anonymous function?
204    ///
205    /// Not available under `no_function`.
206    #[cfg(not(feature = "no_function"))]
207    #[inline(always)]
208    #[must_use]
209    pub fn is_anonymous(&self) -> bool {
210        crate::func::is_anonymous_fn(&self.name)
211    }
212    /// Call the function pointer with curried arguments (if any).
213    /// The function may be script-defined (not available under `no_function`) or native Rust.
214    ///
215    /// This method is intended for calling a function pointer directly, possibly on another [`Engine`].
216    /// Therefore, the [`AST`] is _NOT_ evaluated before calling the function.
217    ///
218    /// # Example
219    ///
220    /// ```
221    /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
222    /// # #[cfg(not(feature = "no_function"))]
223    /// # {
224    /// use rhai::{Engine, FnPtr};
225    ///
226    /// let engine = Engine::new();
227    ///
228    /// let ast = engine.compile("fn foo(x, y) { len(x) + y }")?;
229    ///
230    /// let mut fn_ptr = FnPtr::new("foo")?;
231    ///
232    /// // Curry values into the function pointer
233    /// fn_ptr.set_curry(vec!["abc".into()]);
234    ///
235    /// // Values are only needed for non-curried parameters
236    /// let result: i64 = fn_ptr.call(&engine, &ast, ( 39_i64, ) )?;
237    ///
238    /// assert_eq!(result, 42);
239    /// # }
240    /// # Ok(())
241    /// # }
242    /// ```
243    #[inline]
244    pub fn call<T: Variant + Clone>(
245        &self,
246        engine: &Engine,
247        ast: &AST,
248        args: impl FuncArgs,
249    ) -> RhaiResultOf<T> {
250        let _ast = ast;
251        let mut arg_values = StaticVec::new_const();
252        args.parse(&mut arg_values);
253
254        let global = &mut engine.new_global_runtime_state();
255
256        #[cfg(not(feature = "no_function"))]
257        global.lib.push(_ast.shared_lib().clone());
258
259        let ctx = (engine, self.fn_name(), None, &*global, Position::NONE).into();
260
261        self.call_raw(&ctx, None, arg_values).and_then(|result| {
262            result.try_cast_result().map_err(|r| {
263                let result_type = engine.map_type_name(r.type_name());
264                let cast_type = match type_name::<T>() {
265                    typ if typ.contains("::") => engine.map_type_name(typ),
266                    typ => typ,
267                };
268                ERR::ErrorMismatchOutputType(cast_type.into(), result_type.into(), Position::NONE)
269                    .into()
270            })
271        })
272    }
273    /// Call the function pointer as a _method_ call with a bound `this` pointer and curried arguments (if any).
274    /// The function may be script-defined (not available under `no_function`) or native Rust.
275    ///
276    /// Not available under `no_object`.
277    ///
278    /// This method is intended for calling a function pointer directly, possibly on another [`Engine`].
279    /// Therefore, the [`AST`] is _NOT_ evaluated before calling the function.
280    #[cfg(not(feature = "no_object"))]
281    #[inline]
282    pub fn call_as_method<T: Variant + Clone>(
283        &self,
284        engine: &Engine,
285        ast: &AST,
286        this_ptr: &mut Dynamic,
287        args: impl FuncArgs,
288    ) -> RhaiResultOf<T> {
289        let _ast = ast;
290        let mut arg_values = StaticVec::new_const();
291        args.parse(&mut arg_values);
292
293        let global = &mut engine.new_global_runtime_state();
294
295        #[cfg(not(feature = "no_function"))]
296        global.lib.push(_ast.shared_lib().clone());
297
298        let ctx = (engine, self.fn_name(), None, &*global, Position::NONE).into();
299
300        self.call_raw(&ctx, Some(this_ptr), arg_values)
301            .and_then(|result| {
302                result.try_cast_result().map_err(|r| {
303                    let result_type = engine.map_type_name(r.type_name());
304                    let cast_type = match type_name::<T>() {
305                        typ if typ.contains("::") => engine.map_type_name(typ),
306                        typ => typ,
307                    };
308                    ERR::ErrorMismatchOutputType(
309                        cast_type.into(),
310                        result_type.into(),
311                        Position::NONE,
312                    )
313                    .into()
314                })
315            })
316    }
317    /// Call the function pointer with curried arguments (if any).
318    /// The function may be script-defined (not available under `no_function`) or native Rust.
319    ///
320    /// This method is intended for calling a function pointer that is passed into a native Rust
321    /// function as an argument.  Therefore, the [`AST`] is _NOT_ evaluated before calling the
322    /// function.
323    #[inline]
324    pub fn call_within_context<T: Variant + Clone>(
325        &self,
326        context: &NativeCallContext,
327        args: impl FuncArgs,
328    ) -> RhaiResultOf<T> {
329        let mut arg_values = StaticVec::new_const();
330        args.parse(&mut arg_values);
331
332        self.call_raw(context, None, arg_values).and_then(|result| {
333            result.try_cast_result().map_err(|r| {
334                let result_type = context.engine().map_type_name(r.type_name());
335                let cast_type = match type_name::<T>() {
336                    typ if typ.contains("::") => context.engine().map_type_name(typ),
337                    typ => typ,
338                };
339                ERR::ErrorMismatchOutputType(cast_type.into(), result_type.into(), Position::NONE)
340                    .into()
341            })
342        })
343    }
344    /// Call the function pointer as a _method_ call with a bound `this` pointer and curried arguments (if any).
345    /// The function may be script-defined (not available under `no_function`) or native Rust.
346    ///
347    /// Not available under `no_object`.
348    ///
349    /// This method is intended for calling a function pointer that is passed into a native Rust
350    /// function as an argument.  Therefore, the [`AST`] is _NOT_ evaluated before calling the
351    /// function.
352    #[cfg(not(feature = "no_object"))]
353    #[inline]
354    pub fn call_as_method_within_context<T: Variant + Clone>(
355        &self,
356        context: &NativeCallContext,
357        this_ptr: &mut Dynamic,
358        args: impl FuncArgs,
359    ) -> RhaiResultOf<T> {
360        let mut arg_values = StaticVec::new_const();
361        args.parse(&mut arg_values);
362
363        self.call_raw(context, Some(this_ptr), arg_values)
364            .and_then(|result| {
365                result.try_cast_result().map_err(|r| {
366                    let result_type = context.engine().map_type_name(r.type_name());
367                    let cast_type = match type_name::<T>() {
368                        typ if typ.contains("::") => context.engine().map_type_name(typ),
369                        typ => typ,
370                    };
371                    ERR::ErrorMismatchOutputType(
372                        cast_type.into(),
373                        result_type.into(),
374                        Position::NONE,
375                    )
376                    .into()
377                })
378            })
379    }
380    /// Call the function pointer with curried arguments (if any).
381    /// The function may be script-defined (not available under `no_function`) or native Rust.
382    ///
383    /// This method is intended for calling a function pointer that is passed into a native Rust
384    /// function as an argument.  Therefore, the [`AST`] is _NOT_ evaluated before calling the
385    /// function.
386    ///
387    /// # WARNING - Low Level API
388    ///
389    /// This function is very low level.
390    ///
391    /// # Arguments
392    ///
393    /// All the arguments are _consumed_, meaning that they're replaced by `()`.
394    /// This is to avoid unnecessarily cloning the arguments.
395    ///
396    /// Do not use the arguments after this call. If they are needed afterwards,
397    /// clone them _before_ calling this function.
398    #[inline]
399    pub fn call_raw(
400        &self,
401        context: &NativeCallContext,
402        this_ptr: Option<&mut Dynamic>,
403        arg_values: impl AsMut<[Dynamic]>,
404    ) -> RhaiResult {
405        let mut arg_values = arg_values;
406        let mut arg_values = arg_values.as_mut();
407        let mut args_data;
408
409        if self.is_curried() {
410            args_data = FnArgsVec::with_capacity(self.curry().len() + arg_values.len());
411            args_data.extend(self.curry().iter().cloned());
412            args_data.extend(arg_values.iter_mut().map(mem::take));
413            arg_values = &mut *args_data;
414        }
415
416        let args = &mut StaticVec::with_capacity(arg_values.len() + 1);
417        args.extend(arg_values.iter_mut());
418
419        match self.typ {
420            // Linked to scripted function?
421            #[cfg(not(feature = "no_function"))]
422            FnPtrType::Script(ref fn_def) if fn_def.params.len() == args.len() => {
423                let global = &mut context.global_runtime_state().clone();
424                global.level += 1;
425
426                let caches = &mut crate::eval::Caches::new();
427
428                return context.engine().call_script_fn(
429                    global,
430                    caches,
431                    &mut crate::Scope::new(),
432                    this_ptr,
433                    #[cfg(not(feature = "no_function"))]
434                    self.env.as_deref(),
435                    #[cfg(feature = "no_function")]
436                    None,
437                    fn_def,
438                    args,
439                    true,
440                    context.call_position(),
441                );
442            }
443            _ => (),
444        }
445
446        let is_method = this_ptr.is_some();
447
448        if let Some(obj) = this_ptr {
449            args.insert(0, obj);
450        }
451
452        context.call_fn_raw(self.fn_name(), is_method, is_method, args)
453    }
454
455    /// _(internals)_ Make a call to a function pointer with either a specified number of arguments,
456    /// or with extra arguments attached.
457    /// Exported under the `internals` feature only.
458    ///
459    /// If `this_ptr` is provided, it is first provided to script-defined functions bound to `this`.
460    ///
461    /// When an appropriate function is not found and `move_this_ptr_to_args` is `Some`, `this_ptr`
462    /// is removed and inserted as the appropriate parameter number.
463    ///
464    /// This is useful for calling predicate closures within an iteration loop where the extra
465    /// argument is the current element's index.
466    ///
467    /// If the function pointer is linked to a scripted function definition, use the appropriate
468    /// number of arguments to call it directly (one version attaches extra arguments).
469    #[expose_under_internals]
470    #[inline(always)]
471    fn call_raw_with_extra_args<const N: usize, const E: usize>(
472        &self,
473        caller_fn: &str,
474        ctx: &NativeCallContext,
475        this_ptr: Option<&mut Dynamic>,
476        args: [Dynamic; N],
477        extras: [Dynamic; E],
478        move_this_ptr_to_args: Option<usize>,
479    ) -> RhaiResult {
480        match move_this_ptr_to_args {
481            Some(m) => {
482                self._call_with_extra_args::<true, N, E>(caller_fn, ctx, this_ptr, args, extras, m)
483            }
484            None => {
485                self._call_with_extra_args::<false, N, E>(caller_fn, ctx, this_ptr, args, extras, 0)
486            }
487        }
488    }
489    /// Make a call to a function pointer with either a specified number of arguments, or with extra
490    /// arguments attached.
491    fn _call_with_extra_args<const MOVE_PTR: bool, const N: usize, const E: usize>(
492        &self,
493        caller_fn: &str,
494        ctx: &NativeCallContext,
495        mut this_ptr: Option<&mut Dynamic>,
496        args: [Dynamic; N],
497        extras: [Dynamic; E],
498        move_this_ptr_to_args: usize,
499    ) -> RhaiResult {
500        match self.typ {
501            #[cfg(not(feature = "no_function"))]
502            FnPtrType::Script(ref fn_def) => {
503                let arity = fn_def.params.len();
504
505                if arity == N + self.curry().len() {
506                    return self.call_raw(ctx, this_ptr, args);
507                }
508                if MOVE_PTR {
509                    if let Some(this_ptr) = this_ptr.as_deref() {
510                        if arity == N + 1 + self.curry().len() {
511                            let mut args2 = FnArgsVec::with_capacity(args.len() + 1);
512                            if move_this_ptr_to_args == 0 {
513                                args2.push(this_ptr.clone());
514                                args2.extend(args);
515                            } else {
516                                args2.extend(args);
517                                args2.insert(move_this_ptr_to_args, this_ptr.clone());
518                            }
519                            return self.call_raw(ctx, None, args2);
520                        }
521                        if arity == N + E + 1 + self.curry().len() {
522                            let mut args2 = FnArgsVec::with_capacity(args.len() + extras.len() + 1);
523                            if move_this_ptr_to_args == 0 {
524                                args2.push(this_ptr.clone());
525                                args2.extend(args);
526                            } else {
527                                args2.extend(args);
528                                args2.insert(move_this_ptr_to_args, this_ptr.clone());
529                            }
530                            args2.extend(extras);
531                            return self.call_raw(ctx, None, args2);
532                        }
533                    }
534                }
535                if arity == N + E + self.curry().len() {
536                    let mut args2 = FnArgsVec::with_capacity(args.len() + extras.len());
537                    args2.extend(args);
538                    args2.extend(extras);
539                    return self.call_raw(ctx, this_ptr, args2);
540                }
541            }
542            _ => (),
543        }
544
545        self.call_raw(ctx, this_ptr.as_deref_mut(), args.clone())
546            .or_else(|err| match *err {
547                ERR::ErrorFunctionNotFound(sig, ..)
548                    if MOVE_PTR && this_ptr.is_some() && sig.starts_with(self.fn_name()) =>
549                {
550                    let mut args2 = FnArgsVec::with_capacity(args.len() + 1);
551                    if move_this_ptr_to_args == 0 {
552                        args2.push(this_ptr.as_mut().unwrap().clone());
553                        args2.extend(args.clone());
554                    } else {
555                        args2.extend(args.clone());
556                        args2.insert(move_this_ptr_to_args, this_ptr.as_mut().unwrap().clone());
557                    }
558                    self.call_raw(ctx, None, args2)
559                }
560                _ => Err(err),
561            })
562            .or_else(|err| match *err {
563                ERR::ErrorFunctionNotFound(sig, ..) if sig.starts_with(self.fn_name()) => {
564                    if MOVE_PTR {
565                        if let Some(ref mut this_ptr) = this_ptr {
566                            let mut args2 = FnArgsVec::with_capacity(args.len() + extras.len() + 1);
567                            if move_this_ptr_to_args == 0 {
568                                args2.push(this_ptr.clone());
569                                args2.extend(args);
570                                args2.extend(extras);
571                            } else {
572                                args2.extend(args);
573                                args2.extend(extras);
574                                args2.insert(move_this_ptr_to_args, this_ptr.clone());
575                            }
576                            return self.call_raw(ctx, None, args2);
577                        }
578                    }
579
580                    let mut args2 = FnArgsVec::with_capacity(args.len() + extras.len());
581                    args2.extend(args);
582                    args2.extend(extras);
583
584                    self.call_raw(ctx, this_ptr, args2)
585                }
586                _ => Err(err),
587            })
588            .map_err(|err| {
589                Box::new(ERR::ErrorInFunctionCall(
590                    caller_fn.to_string(),
591                    ctx.call_source().unwrap_or("").to_string(),
592                    err,
593                    Position::NONE,
594                ))
595            })
596    }
597}
598
599impl TryFrom<ImmutableString> for FnPtr {
600    type Error = RhaiError;
601
602    #[inline(always)]
603    fn try_from(value: ImmutableString) -> RhaiResultOf<Self> {
604        if is_valid_function_name(&value) {
605            Ok(Self {
606                name: value,
607                curry: ThinVec::new(),
608                #[cfg(not(feature = "no_function"))]
609                env: None,
610                typ: FnPtrType::Normal,
611            })
612        } else if is_reserved_keyword_or_symbol(&value).0
613            || Token::lookup_symbol_from_syntax(&value).is_some()
614        {
615            Err(ERR::ErrorParsing(PERR::Reserved(value.to_string()), Position::NONE).into())
616        } else {
617            Err(ERR::ErrorFunctionNotFound(value.to_string(), Position::NONE).into())
618        }
619    }
620}
621
622#[cfg(not(feature = "no_function"))]
623impl<T: Into<Shared<crate::ast::ScriptFuncDef>>> From<T> for FnPtr {
624    #[inline(always)]
625    fn from(value: T) -> Self {
626        let fn_def = value.into();
627
628        Self {
629            name: fn_def.name.clone(),
630            curry: ThinVec::new(),
631            #[cfg(not(feature = "no_function"))]
632            env: None,
633            typ: FnPtrType::Script(fn_def),
634        }
635    }
636}
637
638impl Index<usize> for FnPtr {
639    type Output = Dynamic;
640
641    #[inline(always)]
642    fn index(&self, index: usize) -> &Self::Output {
643        self.curry.index(index)
644    }
645}
646
647impl IndexMut<usize> for FnPtr {
648    #[inline(always)]
649    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
650        self.curry.index_mut(index)
651    }
652}
653
654impl Extend<Dynamic> for FnPtr {
655    #[inline(always)]
656    fn extend<T: IntoIterator<Item = Dynamic>>(&mut self, iter: T) {
657        self.curry.extend(iter);
658    }
659}