Rename downcast
to try_cast
and add cast
for Dynamic.
This commit is contained in:
parent
c4a51b1390
commit
246f5fbbe6
54
README.md
54
README.md
@ -279,7 +279,6 @@ The following primitive types are supported natively:
|
||||
| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. |
|
||||
| **Nothing/void/nil/null** (or whatever you want to call it) | `()` | `"()"` | `""` _(empty string)_ |
|
||||
|
||||
[`Dynamic`]: #values-and-types
|
||||
[`()`]: #values-and-types
|
||||
|
||||
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.
|
||||
@ -313,6 +312,55 @@ if type_of(x) == "string" {
|
||||
}
|
||||
```
|
||||
|
||||
Dynamic values
|
||||
--------------
|
||||
|
||||
[`Dynamic`]: #dynamic-values
|
||||
|
||||
A `Dynamic` value can be _any_ type.
|
||||
|
||||
Because [`type_of()`] a `Dynamic` value returns the type of the actual value, it is usually used to perform type-specific
|
||||
actions based on the actual value's type.
|
||||
|
||||
```rust
|
||||
let mystery = get_some_dynamic_value();
|
||||
|
||||
if type_of(mystery) == "i64" {
|
||||
print("Hey, I got an integer here!");
|
||||
} else if type_of(mystery) == "f64" {
|
||||
print("Hey, I got a float here!");
|
||||
} else if type_of(mystery) == "string" {
|
||||
print("Hey, I got a string here!");
|
||||
} else if type_of(mystery) == "bool" {
|
||||
print("Hey, I got a boolean here!");
|
||||
} else if type_of(mystery) == "array" {
|
||||
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) == "TestStruct" {
|
||||
print("Hey, I got the TestStruct custom type here!");
|
||||
} else {
|
||||
print("I don't know what this is: " + type_of(mystery));
|
||||
}
|
||||
```
|
||||
|
||||
In Rust, sometimes a `Dynamic` forms part of the return value - a good example is elements within an `Array` which are `Dynamic`,
|
||||
or property values in an object map. In order to get the _real_ value, the actual value type _must_ be known in advance.
|
||||
There is no easy way for Rust to detect, at run-time, what type the `Dynamic` value is.
|
||||
|
||||
To use a `Dynamic` value in Rust, use the `cast` function to convert the value into a specific, known type.
|
||||
Alternatively, use the `try_cast` function which does not panic but returns an error when the cast fails.
|
||||
|
||||
```rust
|
||||
let list: Array = engine.eval("...")?; // return type is 'Array'
|
||||
let item = list[0]; // an element in an 'Array' is 'Dynamic'
|
||||
|
||||
let value = item.cast::<i64>(); // if the element is 'i64', this succeeds; otherwise it panics
|
||||
let value: i64 = item.cast(); // type can also be inferred
|
||||
|
||||
let value = item.try_cast::<i64>()?; // 'try_cast' does not panic when the cast fails, but returns an error
|
||||
```
|
||||
|
||||
Value conversions
|
||||
-----------------
|
||||
|
||||
@ -1642,9 +1690,9 @@ Function volatility considerations
|
||||
---------------------------------
|
||||
|
||||
Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_ on the external
|
||||
environment and not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value!
|
||||
environment and is not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value!
|
||||
The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_, so when it finds constant arguments
|
||||
it will eagerly run execute the function call. This causes the script to behave differently from the intended semantics because
|
||||
it will eagerly execute the function call. This causes the script to behave differently from the intended semantics because
|
||||
essentially the result of the function call will always be the same value.
|
||||
|
||||
Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions.
|
||||
|
34
src/any.rs
34
src/any.rs
@ -89,7 +89,14 @@ impl Clone for Dynamic {
|
||||
/// An extension trait that allows down-casting a `Dynamic` value to a specific type.
|
||||
pub trait AnyExt: Sized {
|
||||
/// Get a copy of a `Dynamic` value as a specific type.
|
||||
fn downcast<T: Any + Clone>(self) -> Result<Box<T>, Self>;
|
||||
fn try_cast<T: Any + Clone>(self) -> Result<T, Self>;
|
||||
|
||||
/// Get a copy of a `Dynamic` value as a specific type.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type).
|
||||
fn cast<T: Any + Clone>(self) -> T;
|
||||
|
||||
/// This trait may only be implemented by `rhai`.
|
||||
#[doc(hidden)]
|
||||
@ -106,19 +113,38 @@ impl AnyExt for Dynamic {
|
||||
///
|
||||
/// let x: Dynamic = 42_u32.into_dynamic();
|
||||
///
|
||||
/// assert_eq!(*x.downcast::<u32>().unwrap(), 42);
|
||||
/// assert_eq!(x.try_cast::<u32>().unwrap(), 42);
|
||||
/// ```
|
||||
fn downcast<T: Any + Clone>(self) -> Result<Box<T>, Self> {
|
||||
fn try_cast<T: Any + Clone>(self) -> Result<T, Self> {
|
||||
if self.is::<T>() {
|
||||
unsafe {
|
||||
let raw: *mut Variant = Box::into_raw(self);
|
||||
Ok(Box::from_raw(raw as *mut T))
|
||||
Ok(*Box::from_raw(raw as *mut T))
|
||||
}
|
||||
} else {
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a copy of the `Dynamic` value as a specific type.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::{Dynamic, Any, AnyExt};
|
||||
///
|
||||
/// let x: Dynamic = 42_u32.into_dynamic();
|
||||
///
|
||||
/// assert_eq!(x.cast::<u32>(), 42);
|
||||
/// ```
|
||||
fn cast<T: Any + Clone>(self) -> T {
|
||||
self.try_cast::<T>().expect("cast failed")
|
||||
}
|
||||
|
||||
fn _closed(&self) -> _Private {
|
||||
_Private
|
||||
}
|
||||
|
36
src/api.rs
36
src/api.rs
@ -692,8 +692,7 @@ impl<'e> Engine<'e> {
|
||||
ast: &AST,
|
||||
) -> Result<T, EvalAltResult> {
|
||||
self.eval_ast_with_scope_raw(scope, false, ast)?
|
||||
.downcast::<T>()
|
||||
.map(|v| *v)
|
||||
.try_cast::<T>()
|
||||
.map_err(|a| {
|
||||
EvalAltResult::ErrorMismatchOutputType(
|
||||
self.map_type_name((*a).type_name()).to_string(),
|
||||
@ -735,9 +734,8 @@ impl<'e> Engine<'e> {
|
||||
/// Evaluate a file, but throw away the result and only return error (if any).
|
||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run.
|
||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
||||
/// and not cleared from run to run.
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
pub fn consume_file(
|
||||
&mut self,
|
||||
@ -750,9 +748,8 @@ impl<'e> Engine<'e> {
|
||||
/// Evaluate a file with own scope, but throw away the result and only return error (if any).
|
||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run.
|
||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
||||
/// and not cleared from run to run.
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
pub fn consume_file_with_scope(
|
||||
&mut self,
|
||||
@ -767,9 +764,8 @@ impl<'e> Engine<'e> {
|
||||
/// Evaluate a string, but throw away the result and only return error (if any).
|
||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
|
||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
||||
/// and not cleared from run to run.
|
||||
pub fn consume(&mut self, retain_functions: bool, input: &str) -> Result<(), EvalAltResult> {
|
||||
self.consume_with_scope(&mut Scope::new(), retain_functions, input)
|
||||
}
|
||||
@ -777,9 +773,8 @@ impl<'e> Engine<'e> {
|
||||
/// Evaluate a string with own scope, but throw away the result and only return error (if any).
|
||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
|
||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
||||
/// and not cleared from run to run.
|
||||
pub fn consume_with_scope(
|
||||
&mut self,
|
||||
scope: &mut Scope,
|
||||
@ -797,9 +792,8 @@ impl<'e> Engine<'e> {
|
||||
/// Evaluate an AST, but throw away the result and only return error (if any).
|
||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
|
||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
||||
/// and not cleared from run to run.
|
||||
pub fn consume_ast(&mut self, retain_functions: bool, ast: &AST) -> Result<(), EvalAltResult> {
|
||||
self.consume_ast_with_scope(&mut Scope::new(), retain_functions, ast)
|
||||
}
|
||||
@ -807,9 +801,8 @@ impl<'e> Engine<'e> {
|
||||
/// Evaluate an `AST` with own scope, but throw away the result and only return error (if any).
|
||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
|
||||
/// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
||||
/// and not cleared from run to run.
|
||||
pub fn consume_ast_with_scope(
|
||||
&mut self,
|
||||
scope: &mut Scope,
|
||||
@ -884,8 +877,7 @@ impl<'e> Engine<'e> {
|
||||
let mut arg_values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect();
|
||||
|
||||
self.call_fn_raw(name, &mut arg_values, None, Position::none(), 0)?
|
||||
.downcast()
|
||||
.map(|b| *b)
|
||||
.try_cast()
|
||||
.map_err(|a| {
|
||||
EvalAltResult::ErrorMismatchOutputType(
|
||||
self.map_type_name((*a).type_name()).into(),
|
||||
|
@ -628,9 +628,9 @@ impl Engine<'_> {
|
||||
|
||||
// val_array[idx]
|
||||
if let Some(arr) = val.downcast_ref::<Array>() {
|
||||
let idx = *self
|
||||
let idx = self
|
||||
.eval_expr(scope, idx_expr, level)?
|
||||
.downcast::<INT>()
|
||||
.try_cast::<INT>()
|
||||
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?;
|
||||
|
||||
return if idx >= 0 {
|
||||
@ -647,9 +647,9 @@ impl Engine<'_> {
|
||||
{
|
||||
// val_map[idx]
|
||||
if let Some(map) = val.downcast_ref::<Map>() {
|
||||
let idx = *self
|
||||
let idx = self
|
||||
.eval_expr(scope, idx_expr, level)?
|
||||
.downcast::<String>()
|
||||
.try_cast::<String>()
|
||||
.map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_expr.position()))?;
|
||||
|
||||
return Ok((
|
||||
@ -662,9 +662,9 @@ impl Engine<'_> {
|
||||
|
||||
// val_string[idx]
|
||||
if let Some(s) = val.downcast_ref::<String>() {
|
||||
let idx = *self
|
||||
let idx = self
|
||||
.eval_expr(scope, idx_expr, level)?
|
||||
.downcast::<INT>()
|
||||
.try_cast::<INT>()
|
||||
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?;
|
||||
|
||||
return if idx >= 0 {
|
||||
@ -795,9 +795,9 @@ impl Engine<'_> {
|
||||
let s = scope.get_mut_by_type::<String>(src);
|
||||
let pos = new_val.1;
|
||||
// Value must be a character
|
||||
let ch = *new_val
|
||||
let ch = new_val
|
||||
.0
|
||||
.downcast::<char>()
|
||||
.try_cast::<char>()
|
||||
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
|
||||
Self::str_replace_char(s, idx.as_num(), ch);
|
||||
Ok(().into_dynamic())
|
||||
@ -830,8 +830,8 @@ impl Engine<'_> {
|
||||
|
||||
if let Some(s) = target.downcast_mut::<String>() {
|
||||
// Value must be a character
|
||||
let ch = *new_val
|
||||
.downcast::<char>()
|
||||
let ch = new_val
|
||||
.try_cast::<char>()
|
||||
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
|
||||
Self::str_replace_char(s, idx.as_num(), ch);
|
||||
return Ok(target);
|
||||
@ -1258,32 +1258,32 @@ impl Engine<'_> {
|
||||
}
|
||||
|
||||
Expr::And(lhs, rhs) => Ok(Box::new(
|
||||
*self
|
||||
self
|
||||
.eval_expr(scope, &*lhs, level)?
|
||||
.downcast::<bool>()
|
||||
.try_cast::<bool>()
|
||||
.map_err(|_| {
|
||||
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position())
|
||||
})?
|
||||
&& // Short-circuit using &&
|
||||
*self
|
||||
self
|
||||
.eval_expr(scope, &*rhs, level)?
|
||||
.downcast::<bool>()
|
||||
.try_cast::<bool>()
|
||||
.map_err(|_| {
|
||||
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position())
|
||||
})?,
|
||||
)),
|
||||
|
||||
Expr::Or(lhs, rhs) => Ok(Box::new(
|
||||
*self
|
||||
self
|
||||
.eval_expr(scope, &*lhs, level)?
|
||||
.downcast::<bool>()
|
||||
.try_cast::<bool>()
|
||||
.map_err(|_| {
|
||||
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position())
|
||||
})?
|
||||
|| // Short-circuit using ||
|
||||
*self
|
||||
self
|
||||
.eval_expr(scope, &*rhs, level)?
|
||||
.downcast::<bool>()
|
||||
.try_cast::<bool>()
|
||||
.map_err(|_| {
|
||||
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position())
|
||||
})?,
|
||||
@ -1334,10 +1334,10 @@ impl Engine<'_> {
|
||||
// If-else statement
|
||||
Stmt::IfThenElse(guard, if_body, else_body) => self
|
||||
.eval_expr(scope, guard, level)?
|
||||
.downcast::<bool>()
|
||||
.try_cast::<bool>()
|
||||
.map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position()))
|
||||
.and_then(|guard_val| {
|
||||
if *guard_val {
|
||||
if guard_val {
|
||||
self.eval_stmt(scope, if_body, level)
|
||||
} else if let Some(stmt) = else_body {
|
||||
self.eval_stmt(scope, stmt.as_ref(), level)
|
||||
@ -1348,8 +1348,8 @@ impl Engine<'_> {
|
||||
|
||||
// While loop
|
||||
Stmt::While(guard, body) => loop {
|
||||
match self.eval_expr(scope, guard, level)?.downcast::<bool>() {
|
||||
Ok(guard_val) if *guard_val => match self.eval_stmt(scope, body, level) {
|
||||
match self.eval_expr(scope, guard, level)?.try_cast::<bool>() {
|
||||
Ok(guard_val) if guard_val => match self.eval_stmt(scope, body, level) {
|
||||
Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (),
|
||||
Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(().into_dynamic()),
|
||||
Err(x) => return Err(x),
|
||||
@ -1425,9 +1425,7 @@ impl Engine<'_> {
|
||||
Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => {
|
||||
let val = self.eval_expr(scope, a, level)?;
|
||||
Err(EvalAltResult::ErrorRuntime(
|
||||
val.downcast::<String>()
|
||||
.map(|s| *s)
|
||||
.unwrap_or_else(|_| "".to_string()),
|
||||
val.try_cast::<String>().unwrap_or_else(|_| "".to_string()),
|
||||
*pos,
|
||||
))
|
||||
}
|
||||
|
@ -2673,41 +2673,21 @@ pub fn parse<'a, 'e>(
|
||||
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option<Expr>, Dynamic) {
|
||||
if value.is::<INT>() {
|
||||
let value2 = value.clone();
|
||||
(
|
||||
Some(Expr::IntegerConstant(
|
||||
*value.downcast::<INT>().expect("value should be INT"),
|
||||
pos,
|
||||
)),
|
||||
value2,
|
||||
)
|
||||
(Some(Expr::IntegerConstant(value.cast(), pos)), value2)
|
||||
} else if value.is::<char>() {
|
||||
let value2 = value.clone();
|
||||
(
|
||||
Some(Expr::CharConstant(
|
||||
*value.downcast::<char>().expect("value should be char"),
|
||||
pos,
|
||||
)),
|
||||
value2,
|
||||
)
|
||||
(Some(Expr::CharConstant(value.cast(), pos)), value2)
|
||||
} else if value.is::<String>() {
|
||||
let value2 = value.clone();
|
||||
(
|
||||
Some(Expr::StringConstant(
|
||||
*value.downcast::<String>().expect("value should be String"),
|
||||
pos,
|
||||
)),
|
||||
value2,
|
||||
)
|
||||
(Some(Expr::StringConstant(value.cast(), pos)), value2)
|
||||
} else if value.is::<bool>() {
|
||||
let value2 = value.clone();
|
||||
(
|
||||
Some(
|
||||
if *value.downcast::<bool>().expect("value should be bool") {
|
||||
Some(if value.cast::<bool>() {
|
||||
Expr::True(pos)
|
||||
} else {
|
||||
Expr::False(pos)
|
||||
},
|
||||
),
|
||||
}),
|
||||
value2,
|
||||
)
|
||||
} else {
|
||||
@ -2715,13 +2695,7 @@ pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option<Expr>, Dyna
|
||||
{
|
||||
if value.is::<FLOAT>() {
|
||||
let value2 = value.clone();
|
||||
return (
|
||||
Some(Expr::FloatConstant(
|
||||
*value.downcast::<FLOAT>().expect("value should be FLOAT"),
|
||||
pos,
|
||||
)),
|
||||
value2,
|
||||
);
|
||||
return (Some(Expr::FloatConstant(value.cast(), pos)), value2);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,14 +73,14 @@ fn test_map_indexing() -> Result<(), EvalAltResult> {
|
||||
fn test_map_assign() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
let x = engine.eval::<Map>(r#"let x = #{a: 1, b: true, "c#": "hello"}; x"#)?;
|
||||
let a = x.get("a").cloned().unwrap();
|
||||
let b = x.get("b").cloned().unwrap();
|
||||
let c = x.get("c#").cloned().unwrap();
|
||||
let x = engine.eval::<Map>(r#"let x = #{a: 1, b: true, "c$": "hello"}; x"#)?;
|
||||
let a = x.get("a").cloned().expect("should have property a");
|
||||
let b = x.get("b").cloned().expect("should have property b");
|
||||
let c = x.get("c$").cloned().expect("should have property c$");
|
||||
|
||||
assert_eq!(*a.downcast::<INT>().unwrap(), 1);
|
||||
assert_eq!(*b.downcast::<bool>().unwrap(), true);
|
||||
assert_eq!(*c.downcast::<String>().unwrap(), "hello");
|
||||
assert_eq!(a.cast::<INT>(), 1);
|
||||
assert_eq!(b.cast::<bool>(), true);
|
||||
assert_eq!(c.cast::<String>(), "hello");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -89,14 +89,14 @@ fn test_map_assign() -> Result<(), EvalAltResult> {
|
||||
fn test_map_return() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
let x = engine.eval::<Map>(r#"#{a: 1, b: true, c: "hello"}"#)?;
|
||||
let a = x.get("a").cloned().unwrap();
|
||||
let b = x.get("b").cloned().unwrap();
|
||||
let c = x.get("c").cloned().unwrap();
|
||||
let x = engine.eval::<Map>(r#"#{a: 1, b: true, "c$": "hello"}"#)?;
|
||||
let a = x.get("a").cloned().expect("should have property a");
|
||||
let b = x.get("b").cloned().expect("should have property b");
|
||||
let c = x.get("c$").cloned().expect("should have property c$");
|
||||
|
||||
assert_eq!(*a.downcast::<INT>().unwrap(), 1);
|
||||
assert_eq!(*b.downcast::<bool>().unwrap(), true);
|
||||
assert_eq!(*c.downcast::<String>().unwrap(), "hello");
|
||||
assert_eq!(a.cast::<INT>(), 1);
|
||||
assert_eq!(b.cast::<bool>(), true);
|
||||
assert_eq!(c.cast::<String>(), "hello");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user