diff --git a/RELEASES.md b/RELEASES.md
index 68175862..205491a4 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -9,6 +9,11 @@ Breaking changes
* The trait function `ModuleResolver::resolve` no longer takes a `Scope` as argument.
+New features
+------------
+
+* Support for _function pointers_ via `Fn(name)` and `Fn.call(...)` syntax - a poor man's first-class function.
+
Enhancements
------------
diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md
index 2ba5e9e7..ea307d34 100644
--- a/doc/src/SUMMARY.md
+++ b/doc/src/SUMMARY.md
@@ -71,6 +71,7 @@ The Rhai Scripting Language
14. [Functions](language/functions.md)
1. [Function Overloading](language/overload.md)
2. [Call Method as Function](language/method.md)
+ 3. [Function Pointers](language/fn-ptr.md)
15. [Print and Debug](language/print-debug.md)
16. [Modules](language/modules/index.md)
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)
diff --git a/doc/src/about/features.md b/doc/src/about/features.md
index bce5bd69..45958e5a 100644
--- a/doc/src/about/features.md
+++ b/doc/src/about/features.md
@@ -35,6 +35,8 @@ Dynamic
* Organize code base with dynamically-loadable [modules].
+* [Function pointers].
+
Safe
----
diff --git a/doc/src/about/non-design.md b/doc/src/about/non-design.md
index 22b70f7d..6ab4f9ed 100644
--- a/doc/src/about/non-design.md
+++ b/doc/src/about/non-design.md
@@ -11,10 +11,13 @@ It doesn't attempt to be a new language. For example:
* No traits... so it is also not Rust. Do your Rusty stuff in Rust.
* No structures/records - define your types in Rust instead; Rhai can seamlessly work with _any Rust type_.
+
There is, however, a built-in [object map] type which is adequate for most uses.
* No first-class functions - Code your functions in Rust instead, and register them with Rhai.
+ There is, however, support for simple [function pointers] allowing runtime dispatch by function name.
+
* No garbage collection - this should be expected, so...
* No closures - do your closure magic in Rust instead; [turn a Rhai scripted function into a Rust closure]({{rootUrl}}/engine/call-fn.md).
diff --git a/doc/src/language/dynamic.md b/doc/src/language/dynamic.md
index e8c8a21b..9ebe7d6f 100644
--- a/doc/src/language/dynamic.md
+++ b/doc/src/language/dynamic.md
@@ -27,6 +27,8 @@ if type_of(mystery) == "i64" {
print("Hey, I got an array here!");
} else if type_of(mystery) == "map" {
print("Hey, I got an object map here!");
+} else if type_of(mystery) == "Fn" {
+ print("Hey, I got a function pointer here!");
} else if type_of(mystery) == "TestStruct" {
print("Hey, I got the TestStruct custom type here!");
} else {
diff --git a/doc/src/language/eval.md b/doc/src/language/eval.md
index babb9242..a624403f 100644
--- a/doc/src/language/eval.md
+++ b/doc/src/language/eval.md
@@ -28,7 +28,7 @@ eval("{ let z = y }"); // to keep a variable local, use a statement blo
print("z = " + z); // <- error: variable 'z' not found
-"print(42)".eval(); // <- nope... method-call style doesn't work
+"print(42)".eval(); // <- nope... method-call style doesn't work with 'eval'
```
Script segments passed to `eval` execute inside the current [`Scope`], so they can access and modify _everything_,
diff --git a/doc/src/language/fn-ptr.md b/doc/src/language/fn-ptr.md
new file mode 100644
index 00000000..fc083183
--- /dev/null
+++ b/doc/src/language/fn-ptr.md
@@ -0,0 +1,56 @@
+Function Pointers
+=================
+
+It is possible to store a _function pointer_ in a variable just like a normal value.
+In fact, internally a function pointer simply stores the _name_ of the function as a string.
+
+Call a function pointer using the `call` method, which needs to be called in method-call style.
+
+
+Built-in Functions
+------------------
+
+The following standard methods (mostly defined in the [`BasicFnPackage`]({{rootUrl}}/rust/packages.md) but excluded if
+using a [raw `Engine`]) operate on [strings]:
+
+| Function | Parameter(s) | Description |
+| -------------------------- | ------------ | --------------------------------------------------------------------- |
+| `name` method and property | _none_ | returns the name of the function encapsulated by the function pointer |
+
+
+Examples
+--------
+
+```rust
+fn foo(x) { 41 + x }
+
+let func = Fn("foo"); // use the 'Fn' function to create a function pointer
+
+print(func); // prints 'Fn(foo)'
+
+let func = fn_name.Fn(); // <- error: 'Fn' cannot be called in method-call style
+
+func.type_of() == "Fn"; // type_of() as function pointer is 'Fn'
+
+func.name == "foo";
+
+func.call(1) == 42; // call a function pointer with the 'call' method
+
+foo(1) == 42; // <- the above de-sugars to this
+
+call(func, 1); //<- error: 'call (Fn, i64)' is not a registered function
+
+let len = Fn("len"); // 'Fn' also works with registered native Rust functions
+
+len.call("hello") == 5;
+
+let add = Fn("+"); // 'Fn' works with built-in operators also
+
+add.call(40, 2) == 42;
+
+let fn_name = "hello"; // the function name does not have to exist yet
+
+let hello = Fn(fn_name + "_world");
+
+hello.call(0); // error: function not found - "hello_world (i64)"
+```
diff --git a/doc/src/language/values-and-types.md b/doc/src/language/values-and-types.md
index b3e55cb4..ddadc21f 100644
--- a/doc/src/language/values-and-types.md
+++ b/doc/src/language/values-and-types.md
@@ -5,20 +5,21 @@ Values and Types
The following primitive types are supported natively:
-| Category | Equivalent Rust types | [`type_of()`] | `to_string()` |
-| --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------- | --------------------- |
-| **Integer number** | `u8`, `i8`, `u16`, `i16`,
`u32`, `i32` (default for [`only_i32`]),
`u64`, `i64` _(default)_ | `"i32"`, `"u64"` etc. | `"42"`, `"123"` etc. |
-| **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. |
-| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` |
-| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. |
-| **Immutable Unicode string** | `rhai::ImmutableString` (implemented as `Rc` or `Arc`) | `"string"` | `"hello"` etc. |
-| **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` |
-| **Object map** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `#{ "a": 1, "b": 2 }` |
-| **Timestamp** (implemented in the [`BasicTimePackage`]({{rootUrl}}/rust/packages.md), disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`](https://crates.io/crates/instant) if not [WASM] build) | `"timestamp"` | _not supported_ |
-| **Dynamic value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ |
-| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. |
-| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. |
-| **Nothing/void/nil/null/Unit** (or whatever it is called) | `()` | `"()"` | `""` _(empty string)_ |
+| Category | Equivalent Rust types | [`type_of()`] | `to_string()` |
+| ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------- | ----------------------- |
+| **Integer number** | `u8`, `i8`, `u16`, `i16`,
`u32`, `i32` (default for [`only_i32`]),
`u64`, `i64` _(default)_ | `"i32"`, `"u64"` etc. | `"42"`, `"123"` etc. |
+| **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. |
+| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` |
+| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. |
+| **Immutable Unicode [string]** | `rhai::ImmutableString` (implemented as `Rc` or `Arc`) | `"string"` | `"hello"` etc. |
+| **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` |
+| **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` |
+| **[Timestamp]** (implemented in the [`BasicTimePackage`]({{rootUrl}}/rust/packages.md), disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`](https://crates.io/crates/instant) if not [WASM] build) | `"timestamp"` | _not supported_ |
+| **[Function pointer]** (disabled with [`no_function`]) | _None_ | `Fn` | `"Fn(foo)"` |
+| **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ |
+| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. |
+| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. |
+| **Nothing/void/nil/null/Unit** (or whatever it is called) | `()` | `"()"` | `""` _(empty string)_ |
All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different -
they even cannot be added together. This is very similar to Rust.
diff --git a/doc/src/links.md b/doc/src/links.md
index 8be0ed02..a9587253 100644
--- a/doc/src/links.md
+++ b/doc/src/links.md
@@ -65,6 +65,8 @@
[function]: {{rootUrl}}/language/functions.md
[functions]: {{rootUrl}}/language/functions.md
+[function pointer]: {{rootUrl}}/language/fn-ptr.md
+[function pointers]: {{rootUrl}}/language/fn-ptr.md
[`Module`]: {{rootUrl}}/language/modules/index.md
[module]: {{rootUrl}}/language/modules/index.md
diff --git a/doc/src/rust/packages/builtin.md b/doc/src/rust/packages/builtin.md
index da33bfe4..4b4fba81 100644
--- a/doc/src/rust/packages/builtin.md
+++ b/doc/src/rust/packages/builtin.md
@@ -18,6 +18,7 @@ Built-In Packages
| `BasicMathPackage` | Basic math functions (e.g. `sin`, `sqrt`) | No | Yes |
| `BasicArrayPackage` | Basic [array] functions (not available under `no_index`) | No | Yes |
| `BasicMapPackage` | Basic [object map] functions (not available under `no_object`) | No | Yes |
+| `BasicFnPackage` | Basic methods for [function pointers]. | Yes | Yes |
| `EvalPackage` | Disable [`eval`] | No | No |
| `CorePackage` | Basic essentials | Yes | Yes |
| `StandardPackage` | Standard library (default for `Engine::new`) | No | Yes |
diff --git a/src/any.rs b/src/any.rs
index 47da2e04..0f02f0d5 100644
--- a/src/any.rs
+++ b/src/any.rs
@@ -1,6 +1,6 @@
//! Helper module which defines the `Any` trait to to allow dynamic value handling.
-use crate::fn_native::SendSync;
+use crate::fn_native::{FnPtr, SendSync};
use crate::module::Module;
use crate::parser::{ImmutableString, INT};
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
@@ -139,6 +139,8 @@ pub enum Union {
#[cfg(not(feature = "no_object"))]
Map(Box