Complete ImmutableString.

This commit is contained in:
Stephen Chung 2020-05-26 14:14:03 +08:00
parent 95e67c48bd
commit b34d5fe3a1
15 changed files with 379 additions and 274 deletions

View File

@ -514,9 +514,9 @@ This is useful on some 32-bit targets where using 64-bit integers incur a perfor
If no floating-point is needed or supported, use the [`no_float`] feature to remove it.
Strings in Rhai are _immutable_, meaning that they can be shared but not modified. In actual, the `ImmutableString` type
is implemented as an `Rc`- or `Arc`-wrapped `String`. Any modification done to a Rhai string will cause the string to be cloned
and the modifications made to the copy.
[Strings] in Rhai are _immutable_, meaning that they can be shared but not modified. In actual, the `ImmutableString` type
is an alias to `Rc<String>` or `Arc<String>` (depending on the [`sync`] feature).
Any modification done to a Rhai string will cause the string to be cloned and the modifications made to the copy.
The `to_string` function converts a standard type into a [string] for display purposes.
@ -612,6 +612,7 @@ The following conversion traits are implemented for `Dynamic`:
* `From<i64>` (`i32` if [`only_i32`])
* `From<f64>` (if not [`no_float`])
* `From<bool>`
* `From<rhai::ImmutableString>`
* `From<String>`
* `From<char>`
* `From<Vec<T>>` (into an [array])

View File

@ -4,8 +4,8 @@ Rhai Release Notes
Version 0.14.2
==============
Regression
----------
Regression fix
--------------
* Do not optimize script with `eval_expression` - it is assumed to be one-off and short.
@ -18,6 +18,10 @@ Breaking changes
* Default maximum limit on levels of nested function calls is fine-tuned and set to a different value.
* Some operator functions are now built in (see _Speed enhancements_ below), so they are available even
under `Engine::new_raw`.
* Strings are now immutable. The type `rhai::ImmutableString` is used instead of `std::string::String`.
This is to avoid excessive cloning of strings. All native-Rust functions taking string parameters
should switch to `rhai::ImmutableString` (which is either `Rc<String>` or `Arc<String>` depending on
whether the `sync` feature is used).
New features
------------
@ -35,6 +39,8 @@ Speed enhancements
significant speed-up.
* Implementations of common operators for standard types are removed from the `ArithmeticPackage` and `LogicPackage`
(and therefore the `CorePackage`) because they are now always available, even under `Engine::new_raw`.
* Operator-assignment statements (e.g. `+=`) are now handled directly and much faster.
* Strings are now _immutable_ and use the `rhai::ImmutableString` type, eliminating large amounts of cloning.
Version 0.14.1

View File

@ -1,6 +1,5 @@
//! Helper module which defines the `Any` trait to to allow dynamic value handling.
use crate::fn_native::shared_unwrap;
use crate::parser::{ImmutableString, INT};
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
@ -21,9 +20,7 @@ use crate::stdlib::{
boxed::Box,
collections::HashMap,
fmt,
rc::Rc,
string::String,
sync::Arc,
vec::Vec,
};
@ -417,13 +414,10 @@ impl Dynamic {
match self.0 {
Union::Unit(value) => unsafe_try_cast(value),
Union::Bool(value) => unsafe_try_cast(value),
Union::Str(value) => {
if type_id == TypeId::of::<ImmutableString>() {
Union::Str(value) if type_id == TypeId::of::<ImmutableString>() => {
unsafe_try_cast(value)
} else {
unsafe_try_cast((*value).clone())
}
}
Union::Str(value) => unsafe_try_cast(value.into_owned()),
Union::Char(value) => unsafe_try_cast(value),
Union::Int(value) => unsafe_try_cast(value),
#[cfg(not(feature = "no_float"))]
@ -464,12 +458,10 @@ impl Dynamic {
match self.0 {
Union::Unit(value) => unsafe_try_cast(value).unwrap(),
Union::Bool(value) => unsafe_try_cast(value).unwrap(),
Union::Str(value) => if type_id == TypeId::of::<ImmutableString>() {
unsafe_try_cast(value)
} else {
unsafe_try_cast((*value).clone())
Union::Str(value) if type_id == TypeId::of::<ImmutableString>() => {
unsafe_try_cast(value).unwrap()
}
.unwrap(),
Union::Str(value) => unsafe_try_cast(value.into_owned()).unwrap(),
Union::Char(value) => unsafe_try_cast(value).unwrap(),
Union::Int(value) => unsafe_try_cast(value).unwrap(),
#[cfg(not(feature = "no_float"))]
@ -588,7 +580,7 @@ impl Dynamic {
/// Returns the name of the actual type if the cast fails.
pub fn take_string(self) -> Result<String, &'static str> {
match self.0 {
Union::Str(s) => Ok(shared_unwrap(s).unwrap_or_else(|s| (*s).clone())),
Union::Str(s) => Ok(s.into_owned()),
_ => Err(self.type_name()),
}
}

View File

@ -2055,6 +2055,7 @@ fn run_builtin_binary_op(
let y = y.downcast_ref::<ImmutableString>().unwrap();
match op {
"+" => return Ok(Some((x + y).into())),
"==" => return Ok(Some((x == y).into())),
"!=" => return Ok(Some((x != y).into())),
">" => return Ok(Some((x > y).into())),

View File

@ -19,6 +19,8 @@ pub type Shared<T> = Rc<T>;
#[cfg(feature = "sync")]
pub type Shared<T> = Arc<T>;
/// Consume a `Shared` resource and return a mutable reference to the wrapped value.
/// If the resource is shared (i.e. has other outstanding references), a cloned copy is used.
pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
#[cfg(not(feature = "sync"))]
{
@ -30,14 +32,19 @@ pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
}
}
pub fn shared_unwrap<T: Clone>(value: Shared<T>) -> Result<T, Shared<T>> {
/// Consume a `Shared` resource, assuming that it is unique (i.e. not shared).
///
/// # Panics
///
/// Panics if the resource is shared (i.e. has other outstanding references).
pub fn shared_take<T: Clone>(value: Shared<T>) -> T {
#[cfg(not(feature = "sync"))]
{
Rc::try_unwrap(value)
Rc::try_unwrap(value).map_err(|_| ()).unwrap()
}
#[cfg(feature = "sync")]
{
Arc::try_unwrap(value)
Arc::try_unwrap(value).map_err(|_| ()).unwrap()
}
}

View File

@ -8,7 +8,7 @@ use crate::fn_native::{CallableFunction, FnAny, FnCallArgs};
use crate::parser::FnAccess;
use crate::result::EvalAltResult;
use crate::stdlib::{any::TypeId, boxed::Box, mem, string::ToString};
use crate::stdlib::{any::TypeId, boxed::Box, mem};
/// Trait to register custom functions with the `Engine`.
pub trait RegisterFn<FN, ARGS, RET> {

View File

@ -50,7 +50,7 @@
//! ## Optional features
//!
//! | Feature | Description |
//! | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
//! | ------------- | ----------------------------------------------------------------------------------------------------------------------------------|
//! | `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! |
//! | `no_function` | Disable script-defined functions if not needed. |
//! | `no_index` | Disable arrays and indexing features if not needed. |

View File

@ -269,141 +269,69 @@ macro_rules! reg_op {
}
def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
// Checked basic arithmetic
#[cfg(not(feature = "unchecked"))]
{
// reg_op!(lib, "+", add, INT);
// reg_op!(lib, "-", sub, INT);
// reg_op!(lib, "*", mul, INT);
// reg_op!(lib, "/", div, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
// reg_op!(lib, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "/", div, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
#[cfg(not(feature = "unchecked"))]
{
// Checked basic arithmetic
reg_op!(lib, "+", add, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "*", mul, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "/", div, i8, u8, i16, u16, i32, u32, u64, i128, u128);
}
}
// Unchecked basic arithmetic
#[cfg(feature = "unchecked")]
{
// reg_op!(lib, "+", add_u, INT);
// reg_op!(lib, "-", sub_u, INT);
// reg_op!(lib, "*", mul_u, INT);
// reg_op!(lib, "/", div_u, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
// reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "*", mul_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "/", div_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "*", mul_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "/", div_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
}
}
// Basic arithmetic for floating-point - no need to check
#[cfg(not(feature = "no_float"))]
{
// reg_op!(lib, "+", add_u, f32, f64);
// reg_op!(lib, "-", sub_u, f32, f64);
// reg_op!(lib, "*", mul_u, f32, f64);
// reg_op!(lib, "/", div_u, f32, f64);
reg_op!(lib, "+", add_u, f32);
reg_op!(lib, "-", sub_u, f32);
reg_op!(lib, "*", mul_u, f32);
reg_op!(lib, "/", div_u, f32);
}
// Bit operations
// reg_op!(lib, "|", binary_or, INT);
// reg_op!(lib, "&", binary_and, INT);
// reg_op!(lib, "^", binary_xor, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
// reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, u32, u64, i128, u128);
}
// Checked bit shifts
#[cfg(not(feature = "unchecked"))]
{
// reg_op!(lib, "<<", shl, INT);
// reg_op!(lib, ">>", shr, INT);
// reg_op!(lib, "%", modulo, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
// reg_op!(lib, "<<", shl, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "<<", shl, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, u32, u64, i128, u128);
}
}
// Unchecked bit shifts
#[cfg(feature = "unchecked")]
{
// reg_op!(lib, "<<", shl_u, INT, INT);
// reg_op!(lib, ">>", shr_u, INT, INT);
// reg_op!(lib, "%", modulo_u, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
// reg_op!(lib, "<<", shl_u, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// Unchecked basic arithmetic
reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "*", mul_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "/", div_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
// Unchecked bit shifts
reg_op!(lib, "<<", shl_u, i64, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
}
}
// Checked power
#[cfg(not(feature = "unchecked"))]
// Basic arithmetic for floating-point - no need to check
#[cfg(not(feature = "no_float"))]
{
// lib.set_fn_2("~", pow_i_i);
reg_op!(lib, "+", add_u, f32);
reg_op!(lib, "-", sub_u, f32);
reg_op!(lib, "*", mul_u, f32);
reg_op!(lib, "/", div_u, f32);
}
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, u32, u64, i128, u128);
}
#[cfg(not(feature = "no_float"))]
{
// Checked power
#[cfg(not(feature = "unchecked"))]
lib.set_fn_2("~", pow_f_i);
}
// Unchecked power
#[cfg(feature = "unchecked")]
{
// lib.set_fn_2("~", pow_i_i_u);
#[cfg(not(feature = "no_float"))]
lib.set_fn_2("~", pow_f_i_u);
}
// Floating-point modulo and power
#[cfg(not(feature = "no_float"))]
{
// reg_op!(lib, "%", modulo_u, f32, f64);
reg_op!(lib, "%", modulo_u, f32);
// lib.set_fn_2("~", pow_f_f);
// Floating-point unary
reg_unary!(lib, "-", neg_u, f32, f64);
reg_unary!(lib, "abs", abs_u, f32, f64);
}
// Checked unary
@ -433,11 +361,4 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
reg_unary!(lib, "abs", abs_u, i8, i16, i32, i64, i128);
}
}
// Floating-point unary
#[cfg(not(feature = "no_float"))]
{
reg_unary!(lib, "-", neg_u, f32, f64);
reg_unary!(lib, "abs", abs_u, f32, f64);
}
});

View File

@ -1,6 +1,5 @@
use crate::def_package;
use crate::module::FuncReturn;
use crate::parser::{ImmutableString, INT};
// Comparison operators
pub fn lt<T: PartialOrd>(x: T, y: T) -> FuncReturn<bool> {
@ -23,12 +22,6 @@ pub fn ne<T: PartialEq>(x: T, y: T) -> FuncReturn<bool> {
}
// Logic operators
fn and(x: bool, y: bool) -> FuncReturn<bool> {
Ok(x && y)
}
fn or(x: bool, y: bool) -> FuncReturn<bool> {
Ok(x || y)
}
fn not(x: bool) -> FuncReturn<bool> {
Ok(!x)
}
@ -40,30 +33,9 @@ macro_rules! reg_op {
}
def_package!(crate:LogicPackage:"Logical operators.", lib, {
// reg_op!(lib, "<", lt, INT, char);
// reg_op!(lib, "<=", lte, INT, char);
// reg_op!(lib, ">", gt, INT, char);
// reg_op!(lib, ">=", gte, INT, char);
// reg_op!(lib, "==", eq, INT, char, bool, ());
// reg_op!(lib, "!=", ne, INT, char, bool, ());
// Special versions for strings - at least avoid copying the first string
// lib.set_fn_2("<", |x: ImmutableString, y: ImmutableString| Ok(*x < y));
// lib.set_fn_2("<=", |x: ImmutableString, y: ImmutableString| Ok(*x <= y));
// lib.set_fn_2(">", |x: ImmutableString, y: ImmutableString| Ok(*x > y));
// lib.set_fn_2(">=", |x: ImmutableString, y: ImmutableString| Ok(*x >= y));
// lib.set_fn_2("==", |x: ImmutableString, y: ImmutableString| Ok(*x == y));
// lib.set_fn_2("!=", |x: ImmutableString, y: ImmutableString| Ok(*x != y));
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
// reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, u32, u64, i128, u128);
@ -74,12 +46,6 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, {
#[cfg(not(feature = "no_float"))]
{
// reg_op!(lib, "<", lt, f32, f64);
// reg_op!(lib, "<=", lte, f32, f64);
// reg_op!(lib, ">", gt, f32, f64);
// reg_op!(lib, ">=", gte, f32, f64);
// reg_op!(lib, "==", eq, f32, f64);
// reg_op!(lib, "!=", ne, f32, f64);
reg_op!(lib, "<", lt, f32);
reg_op!(lib, "<=", lte, f32);
reg_op!(lib, ">", gt, f32);
@ -88,12 +54,5 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, {
reg_op!(lib, "!=", ne, f32);
}
// `&&` and `||` are treated specially as they short-circuit.
// They are implemented as special `Expr` instances, not function calls.
//reg_op!(lib, "||", or, bool);
//reg_op!(lib, "&&", and, bool);
// lib.set_fn_2("|", or);
// lib.set_fn_2("&", and);
lib.set_fn_1("!", not);
});

View File

@ -6,10 +6,7 @@ use crate::engine::Map;
use crate::module::FuncReturn;
use crate::parser::{ImmutableString, INT};
use crate::stdlib::{
string::{String, ToString},
vec::Vec,
};
use crate::stdlib::{string::ToString, vec::Vec};
fn map_get_keys(map: &mut Map) -> FuncReturn<Vec<Dynamic>> {
Ok(map.iter().map(|(k, _)| k.to_string().into()).collect())

View File

@ -4,7 +4,7 @@ use crate::fn_native::{CallableFunction, IteratorFn, Shared};
use crate::module::Module;
use crate::utils::StaticVec;
use crate::stdlib::{any::TypeId, boxed::Box, collections::HashMap, rc::Rc, sync::Arc, vec::Vec};
use crate::stdlib::any::TypeId;
pub(crate) mod arithmetic;
mod array_basic;

View File

@ -1,6 +1,5 @@
use crate::def_package;
use crate::engine::{FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT};
use crate::fn_native::shared_make_mut;
use crate::module::FuncReturn;
use crate::parser::{ImmutableString, INT};
@ -13,7 +12,7 @@ use crate::engine::Map;
use crate::stdlib::{
fmt::{Debug, Display},
format,
string::{String, ToString},
string::ToString,
};
// Register print and debug
@ -79,58 +78,9 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin
lib.set_fn_1_mut(KEYWORD_DEBUG, format_map);
}
lib.set_fn_2(
"+",
|s: ImmutableString, ch: char| {
if s.is_empty() {
return Ok(ch.to_string().into());
}
let mut s = (*s).clone();
s.push(ch);
Ok(s)
},
);
lib.set_fn_2(
"+",
|s:ImmutableString, s2:ImmutableString| {
if s.is_empty() {
return Ok(s2);
} else if s2.is_empty() {
return Ok(s);
}
let mut s = (*s).clone();
s.push_str(s2.as_str());
Ok(s.into())
},
);
lib.set_fn_2_mut("+=", |s: &mut ImmutableString, ch: char| {
shared_make_mut(s).push(ch);
Ok(())
});
lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| {
shared_make_mut(s).push(ch);
Ok(())
});
lib.set_fn_2_mut(
"+=",
|s: &mut ImmutableString, s2: ImmutableString| {
if !s2.is_empty() {
shared_make_mut(s).push_str(s2.as_str());
}
Ok(())
}
);
lib.set_fn_2_mut(
"append",
|s: &mut ImmutableString, s2: ImmutableString| {
if !s2.is_empty() {
shared_make_mut(s).push_str(s2.as_str());
}
Ok(())
}
);
lib.set_fn_2("+", |s: ImmutableString, ch: char| Ok(s + ch));
lib.set_fn_2_mut("+=", |s: &mut ImmutableString, ch: char| { *s += ch; Ok(()) });
lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| { *s += ch; Ok(()) });
lib.set_fn_2_mut("+=", |s: &mut ImmutableString, s2: ImmutableString| { *s += &s2; Ok(()) });
lib.set_fn_2_mut("append", |s: &mut ImmutableString, s2: ImmutableString| { *s += &s2; Ok(()) });
});

View File

@ -1,5 +1,4 @@
use crate::def_package;
use crate::fn_native::shared_make_mut;
use crate::module::FuncReturn;
use crate::parser::{ImmutableString, INT};
use crate::utils::StaticVec;
@ -48,12 +47,12 @@ fn sub_string(s: ImmutableString, start: INT, len: INT) -> FuncReturn<ImmutableS
}
fn crop_string(s: &mut ImmutableString, start: INT, len: INT) -> FuncReturn<()> {
let offset = if s.is_empty() || len <= 0 {
shared_make_mut(s).clear();
s.make_mut().clear();
return Ok(());
} else if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
shared_make_mut(s).clear();
s.make_mut().clear();
return Ok(());
} else {
start as usize
@ -67,7 +66,7 @@ fn crop_string(s: &mut ImmutableString, start: INT, len: INT) -> FuncReturn<()>
len as usize
};
let copy = shared_make_mut(s);
let copy = s.make_mut();
copy.clear();
copy.extend(chars.iter().skip(offset).take(len));
@ -166,17 +165,17 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
},
);
lib.set_fn_1_mut("clear", |s: &mut ImmutableString| {
shared_make_mut(s).clear();
s.make_mut().clear();
Ok(())
});
lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| {
shared_make_mut(s).push(ch);
s.make_mut().push(ch);
Ok(())
});
lib.set_fn_2_mut(
"append",
|s: &mut ImmutableString, add: ImmutableString| {
shared_make_mut(s).push_str(add.as_str());
s.make_mut().push_str(add.as_str());
Ok(())
}
);
@ -198,11 +197,11 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
|s: &mut ImmutableString, len: INT| {
if len > 0 {
let chars: StaticVec<_> = s.chars().collect();
let copy = shared_make_mut(s);
let copy = s.make_mut();
copy.clear();
copy.extend(chars.into_iter().take(len as usize));
} else {
shared_make_mut(s).clear();
s.make_mut().clear();
}
Ok(())
},
@ -210,7 +209,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
lib.set_fn_3_mut(
"pad",
|s: &mut ImmutableString, len: INT, ch: char| {
let copy = shared_make_mut(s);
let copy = s.make_mut();
for _ in 0..copy.chars().count() - len as usize {
copy.push(ch);
}

View File

@ -4,7 +4,6 @@ use crate::any::{Dynamic, Union};
use crate::calc_fn_hash;
use crate::engine::{make_getter, make_setter, Engine, FunctionsLib};
use crate::error::{LexError, ParseError, ParseErrorType};
use crate::fn_native::Shared;
use crate::optimize::{optimize_into_ast, OptimizationLevel};
use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::token::{Position, Token, TokenIterator};
@ -49,11 +48,10 @@ pub type INT = i32;
#[cfg(not(feature = "no_float"))]
pub type FLOAT = f64;
/// The system immutable string type.
pub type ImmutableString = Shared<String>;
type PERR = ParseErrorType;
pub use crate::utils::ImmutableString;
/// Compiled AST (abstract syntax tree) of a Rhai script.
///
/// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.

View File

@ -4,14 +4,18 @@
//!
//! The `StaticVec` type has some `unsafe` blocks to handle conversions between `MaybeUninit` and regular types.
use crate::fn_native::{shared_make_mut, shared_take, Shared};
use crate::stdlib::{
any::TypeId,
borrow::Borrow,
fmt,
hash::{Hash, Hasher},
iter::FromIterator,
mem,
mem::MaybeUninit,
ops::{Drop, Index, IndexMut},
ops::{Add, AddAssign, Deref, Drop, Index, IndexMut},
str::FromStr,
vec::Vec,
};
@ -560,3 +564,273 @@ impl<T> From<Vec<T>> for StaticVec<T> {
arr
}
}
/// The system immutable string type.
///
/// An `ImmutableString` wraps an `Rc<String>` (or `Arc<String>` under the `sync` feature)
/// so that it can be simply shared and not cloned.
///
/// # Examples
///
/// ```
/// use rhai::ImmutableString;
///
/// let s1: ImmutableString = "hello".into();
///
/// // No actual cloning of the string is involved below.
/// let s2 = s1.clone();
/// let s3 = s2.clone();
///
/// assert_eq!(s1, s2);
///
/// // Clones the underlying string (because it is already shared) and extracts it.
/// let mut s: String = s1.into_owned();
///
/// // Changing the clone has no impact on the previously shared version.
/// s.push_str(", world!");
///
/// // The old version still exists.
/// assert_eq!(s2, s3);
/// assert_eq!(s2.as_str(), "hello");
///
/// // Not equals!
/// assert_ne!(s2.as_str(), s.as_str());
/// assert_eq!(s, "hello, world!");
/// ```
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
pub struct ImmutableString(Shared<String>);
impl Deref for ImmutableString {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<String> for ImmutableString {
fn as_ref(&self) -> &String {
&self.0
}
}
impl Borrow<str> for ImmutableString {
fn borrow(&self) -> &str {
self.0.as_str()
}
}
impl From<&str> for ImmutableString {
fn from(value: &str) -> Self {
Self(value.to_string().into())
}
}
impl From<String> for ImmutableString {
fn from(value: String) -> Self {
Self(value.into())
}
}
impl From<Box<String>> for ImmutableString {
fn from(value: Box<String>) -> Self {
Self(value.into())
}
}
impl From<ImmutableString> for String {
fn from(value: ImmutableString) -> Self {
value.into_owned()
}
}
impl FromStr for ImmutableString {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.to_string().into()))
}
}
impl FromIterator<char> for ImmutableString {
fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into())
}
}
impl<'a> FromIterator<&'a char> for ImmutableString {
fn from_iter<T: IntoIterator<Item = &'a char>>(iter: T) -> Self {
Self(iter.into_iter().cloned().collect::<String>().into())
}
}
impl<'a> FromIterator<&'a str> for ImmutableString {
fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into())
}
}
impl<'a> FromIterator<String> for ImmutableString {
fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into())
}
}
impl fmt::Display for ImmutableString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.0.as_str(), f)
}
}
impl fmt::Debug for ImmutableString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.0.as_str(), f)
}
}
impl Add for ImmutableString {
type Output = Self;
fn add(mut self, rhs: Self) -> Self::Output {
if rhs.is_empty() {
self
} else if self.is_empty() {
rhs
} else {
self.make_mut().push_str(rhs.0.as_str());
self
}
}
}
impl Add for &ImmutableString {
type Output = ImmutableString;
fn add(self, rhs: Self) -> Self::Output {
if rhs.is_empty() {
self.clone()
} else if self.is_empty() {
rhs.clone()
} else {
let mut s = self.clone();
s.make_mut().push_str(rhs.0.as_str());
s
}
}
}
impl AddAssign<&ImmutableString> for ImmutableString {
fn add_assign(&mut self, rhs: &ImmutableString) {
if !rhs.is_empty() {
if self.is_empty() {
self.0 = rhs.0.clone();
} else {
self.make_mut().push_str(rhs.0.as_str());
}
}
}
}
impl Add<&str> for ImmutableString {
type Output = Self;
fn add(mut self, rhs: &str) -> Self::Output {
if rhs.is_empty() {
self
} else {
self.make_mut().push_str(rhs);
self
}
}
}
impl Add<&str> for &ImmutableString {
type Output = ImmutableString;
fn add(self, rhs: &str) -> Self::Output {
if rhs.is_empty() {
self.clone()
} else {
let mut s = self.clone();
s.make_mut().push_str(rhs);
s
}
}
}
impl AddAssign<&str> for ImmutableString {
fn add_assign(&mut self, rhs: &str) {
if !rhs.is_empty() {
self.make_mut().push_str(rhs);
}
}
}
impl Add<String> for ImmutableString {
type Output = Self;
fn add(mut self, rhs: String) -> Self::Output {
if rhs.is_empty() {
self
} else if self.is_empty() {
rhs.into()
} else {
self.make_mut().push_str(&rhs);
self
}
}
}
impl Add<String> for &ImmutableString {
type Output = ImmutableString;
fn add(self, rhs: String) -> Self::Output {
if rhs.is_empty() {
self.clone()
} else if self.is_empty() {
rhs.into()
} else {
let mut s = self.clone();
s.make_mut().push_str(&rhs);
s
}
}
}
impl Add<char> for ImmutableString {
type Output = Self;
fn add(mut self, rhs: char) -> Self::Output {
self.make_mut().push(rhs);
self
}
}
impl Add<char> for &ImmutableString {
type Output = ImmutableString;
fn add(self, rhs: char) -> Self::Output {
let mut s = self.clone();
s.make_mut().push(rhs);
s
}
}
impl AddAssign<char> for ImmutableString {
fn add_assign(&mut self, rhs: char) {
self.make_mut().push(rhs);
}
}
impl ImmutableString {
/// Consume the `ImmutableString` and convert it into a `String`.
/// If there are other references to the same string, a cloned copy is returned.
pub fn into_owned(mut self) -> String {
self.make_mut(); // Make sure it is unique reference
shared_take(self.0) // Should succeed
}
/// Make sure that the `ImmutableString` is unique (i.e. no other outstanding references).
/// Then return a mutable reference to the `String`.
pub fn make_mut(&mut self) -> &mut String {
shared_make_mut(&mut self.0)
}
}