Repository: rhaiscript/rhai (language: rust)

Issue:
How to use with no-std
I'm trying out rust on a cheap microcontroller following this tutorial https://polyfractal.com/post/rustl8710/

The tutorial works great and the sample echo program runs over serial.

I'd love to add in a rust scripting language and I really like the design of Rhai.  But when I add the library, the compile fails because of std is missing.

```
info: using existing install for 'nightly-x86_64-unknown-linux-gnu'
info: override toolchain for '/home/tim/rustl8710/src/rust' set to 'nightly-x86_64-unknown-linux-gnu'

  nightly-x86_64-unknown-linux-gnu unchanged - rustc 1.16.0-nightly (4ce7accaa 2017-01-17)

cd src/rust && xargo build --target thumbv7m-none-eabi
   Compiling rhai v0.4.0
error[E0463]: can't find crate for `std`
  |
  = note: the `thumbv7m-none-eabi` target may not be installed
```

I'm fairly new to rust still and am not quite clear on what this all means.  Is Rhai meant to be used in such embedded use cases?  It sure would be neat if it or something like it did work.


Discussion / hints:
(unfortunately this repo is largely unmaintained at this point, sadly... though I'd love to have time to work on it)

I'm not familiar with xargo, but it may be possible to move rhai over to no-std in the future.  I know there are some efforts to make this easier in the future.  In theory, most of it should be pretty portable.
I'm new too, but my thoughts:  xargo should build the std crate for the target, at least some tutorials say it will.  I think " the `thumbv7m-none-eabi` target may not be installed" means that you are missing some component or configuration for llvm or rust?  rustc --print target-list  will print the list of target specifications (not the list of distributed std libraries?), and your target is in that list on my system.  I had this error message also when doing "xargo build --examples hello" but other builds worked.
In the future, we may provide a no-std feature, though. Interesting idea
I'm in need of a `#[no_std]` scripting language and REPL for https://github.com/thejpster/monotron and porting this looks a lot simpler than writing my own from scratch! I'm going to take a look and see what's involved and report back.
We also have a project that will run on a microcontroller and has need for a scripting language. For our case we would be okay with a `#![no_std]` solution that requires [alloc](https://docs.rust-embedded.org/book/collections/). This way we have access to Vec, Box, etc.
Started working on this here: https://github.com/VictorKoenders/rhai/tree/no_std

I ran into 2 big issues:
- hashmap is not available in alloc: https://github.com/rust-lang/rust/issues/56192
This can be solved by depending on [hashbrown](https://crates.io/crates/hashbrown) directly, however this introduces additional dependencies.
- error::Error is not available in core/alloc
Because of backwards compatibility, `std::error::Error` cannot be used in core or alloc, because it depends on OS-specific implementations that are now deprecated.
There is a crate that exposes a trait that can be used in core: [core-error](https://crates.io/crates/core-error), but it is unclear if this is the direction that Rust is going to take.

Furthermore I've disabled everything depending on `std::fs::File` for obvious reasons.

Edit: added `hashbrown` and `core-error`, currently blocked on [math not being available in core](https://github.com/rust-lang/rfcs/issues/2505). Yes, really.

Edit 2: core::intrinsics did have the math functions, so I added those and updated the travis build to also test `no_std` on nightly. The project compiles but I'll need to test it in a no_std environment to see if it actually runs and how much memory it uses.
> Started working on this here: https://github.com/VictorKoenders/rhai/tree/no_std

@VictorKoenders   I have pushed a large amount of changes recently.  Can you see if it is easy to for you to update your branch?
It was easier to re-do my changes. I've updated my branch to the latest master version.

One thing I ran into is that `Engine::new` sets `on_print` and `on_debug` to use `println`, which is not available on no_std targets (because there's no stdout). Currently in `no_std` mode, the messages silently get dropped.

I've added an alternative function `new_with_callbacks` where the developer can configure their own print and debug outputs, however I think it'd be better to use a builder for this in the future. Something like:
```Rust
let engine = Engine::custom()
    .on_print(|x| println!("{}", x))
    .on_debug(|x| eprintln!("{}", x))
    .map_type("alloc::string::String", "string") // fills `type_names`
    .map_type("alloc::vec::Vec<alloc::boxed::Box<dyn rhai::any::Any>>>", "array")
    .map_type("alloc::boxed::Box<dyn rhai::any::Any>", "dynamic")
    .build();
```

>  I think it'd be better to use a builder for this in the future

The would be nice, especially some API to do function registrations and setup the initial scope with variables.

> One thing I ran into is that `Engine::new` sets `on_print` and `on_debug` to use `println`, which is not available on no_std targets

Maybe in `no-std` builds we need to make the default no-op's?

Or make them no-op's by default for all targets?  If someone wants to print them, it'd be easy to register a callback with `println!`...
I added a new `no-std` feature that I intend to use to cut off some of this utility functions as well as things like `println!`.  Eventually I intend the `no-std` feature flag to turn on exact `no-std` mode, although I have no idea whether it will work.

Go clone from https://github.com/schungx/rhai to check it out.

I should be able to do a final test tonight. One of the issues I ran into is that the microcontrollers I have only have several KB of storage space (16KB and 64KB), which needs to contain the binary and the .rhai file. Rhai is incredibly small, only about 155KB, but that's still too big for the devices I currently have. I managed to shave off 80KB by disabling the builtin functions, but that still leaves the firmware at 77KB, just over the limit of 64KB I have.

Another thing that I want to do before making a PR is to update the README, as this currently (correctly) states that rhai has no dependencies, but when using no_std we'll have to use some dependencies (or writing our own hashmap, error trait and math functions).
It is better that you base your work on my working repo so we won't have a huge headache later on trying to merge... :-)
> but that still leaves the firmware at 77KB, just over the limit of 64KB I have.

Did you compile release mode with `lto = true`?  I found that using `lto` typically shaves 30-40% off the size of the final binary.

If it is still large, I can see what things I can trim out.
But I've already done all the work in my repo, and it's easy to rebase it.

The project I'm trying to run is on https://github.com/victorkoenders/embedded-rhai. These are my cargo flags:
```
[profile.release]
codegen-units = 1 # better optimizations
debug = true # symbols are nice and they don't increase the size on Flash
lto = true # better optimizations
opt-level = "z"
```

The output of `cargo bloat` is:
```
File  .text    Size         Crate Name
0.6%  11.6%  8.4KiB          rhai <rhai::parser::TokenIterator as core::iter::traits::iterator::Ite...
0.5%  10.0%  7.2KiB           std core::fmt::float::float_to_decimal_common_shortest
0.5%   9.1%  6.6KiB           std core::fmt::float::float_to_decimal_common_exact
0.2%   4.6%  3.3KiB embedded_rhai embedded_rhai::__cortex_m_rt_main
0.2%   4.4%  3.2KiB          rhai rhai::engine::Engine::eval_expr
0.1%   2.5%  1.8KiB          rhai rhai::parser::parse_binop
0.1%   1.9%  1.4KiB          rhai rhai::engine::Engine::get_dot_val_helper
0.1%   1.9%  1.4KiB          rhai rhai::engine::Engine::eval_stmt
0.1%   1.9%  1.4KiB          rhai rhai::parser::parse_primary
0.1%   1.7%  1.3KiB          rhai rhai::engine::Engine::call_fn_raw
0.1%   1.6%  1.2KiB          rhai <rhai::parser::Token as core::fmt::Debug>::fmt
0.1%   1.4%  1.0KiB          rhai rhai::parser::parse_stmt
0.1%   1.3%   1000B           std __udivmoddi4
0.1%   1.2%    910B           std core::char::methods::<impl char>::escape_debug_ext
0.1%   1.0%    776B           std core::num::bignum::Big32x40::mul_digits
0.1%   1.0%    774B           std __divdf3
0.1%   1.0%    734B          rhai <&T as core::fmt::Debug>::fmt
0.1%   1.0%    732B           std core::fmt::Formatter::pad
0.1%   1.0%    726B           std __muldf3
0.0%   0.9%    680B           std core::fmt::Formatter::write_formatted_parts
2.0%  38.6% 28.0KiB               And 341 smaller methods. Use -n N to show more.
5.2% 100.0% 72.5KiB               .text section size, the file size is 1.4MiB
```

I'm looking into precompiling the `AST` and having the engine evaluate the statements on the microcontroller. However I think that's outside of the scope of this issue. 
I was reading somewhere that the recommendation is to add a feature called `std` that is enabled by default rather than `no_std` which is a double negative.

https://rust-lang.github.io/api-guidelines/naming.html#feature-names-are-free-of-placeholder-words-c-feature

The problem with using `no_std` is that's also the name of the crate-level attribute.
Normally I'd agree, but in this case we actually need to add dependencies when we switch to no_std mode, and I don't know a way to include a dependency when a feature is taken away.
- HashMap is not in alloc, so we need to add hashbrown
- error::Error is not in core, so we need to add core-error
- f64::pow and f64::powi are not in core, and using core::intrinsics gives a link-time error, so we add libm
> But I've already done all the work in my repo, and it's easy to rebase it.

I'm about to do another PR with a large amounts of coding and documentation cleanup.  I think it might be easier if you redo you changes based on what I have right now...

Alternatively, I can manually redo your changes to my my repo, but then I'll have no way to test that it works and I haven't made a mistake.
I've created a new PR https://github.com/jonathandturner/rhai/pull/104 with the latest clean-up for reference.
@VictorKoenders   I've back ported your `no_std` changes to the latest version.  There is a `no_std` branch: https://github.com/schungx/rhai/tree/no-std

It almost compiles on my machine, except for `core::intrinsics` because I'm running `stable`.

Can you test if it works OK?

My PR #103 doesn't involve `core::intrinsics`, so I'm not sure what you're porting.

Feel free to poke me to rebase my changes when you're done with yours. I can reapply these changes trivially and do another test to see if it runs on a microcontroller.
> My PR #103 doesn't involve `core::intrinsics`,

~~~
![cfg_attr(feature = "no_std", feature(core_intrinsics))]
~~~



> Feel free to poke me to rebase my changes

Great!  I'm a noob Git user and have no idea how to rebase something...

You can see in my branch that I've spun out `_std` into a separate module so I don't have to pollute `lib.rs` with `no_std` stuff.  I think this is a better way to do it.

Also, `builtins.rs` has some changes.  In particular, a _core_ library with minimal stuff (like arithmetic and logic) is always compiled in and I have used the feature `no_stdlib` which is opt-out to exclude other built-in features, instead of `default_buildins` which is opt-in.

@VictorKoenders   I've merged the latest round of changes.  You can start your rebasing.

@VictorKoenders   Commit https://github.com/jonathandturner/rhai/pull/105/commits/b1b25d3043e194ca6e74a40ba6c20aabe864d7a4 adds a dependency to `num-traits` in order to avoid scripts panic-ing the system by doing checked arithmetic.

I understand that the feature `libm` of `num-traits` should be turned on when building a `no-std` build.  Please take this into consideration when you rebase your changes.

`num-traits` is not large so I'm quite sure it should not bloat your code size...  in a future commit, I'll enable disabling arithmetic checking with a new feature.

Fix patch:
diff --git a/.ci/build.sh b/.ci/build.sh
new file mode 100755
index 000000000..999f60c90
--- /dev/null
+++ b/.ci/build.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+set -ex
+
+cargo build --verbose
+cargo test --verbose
+
+if [ "$TRAVIS_RUST_VERSION" = "nightly" ]
+then
+    cargo build --verbose --features no_stdlib
+    cargo test --verbose --features no_stdlib
+fi
+
diff --git a/.travis.yml b/.travis.yml
index 8c91a7415..80702bdc1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,3 +6,5 @@ rust:
 matrix:
   allow_failures:
     - rust: nightly
+
+script: bash ./.ci/build.sh
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
index 308685bff..f495d608d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,14 +15,34 @@ include = [
 ]
 
 [dependencies]
-num-traits = "*"
+num-traits = "0.2.11"
+
+[dependencies.libm]
+version = "0.2.1"
+optional = true
+
+[dependencies.core-error]
+version = "0.0.1-rc4"
+features = ["alloc"]
+optional = true
+
+[dependencies.hashbrown]
+version = "0.7.1"
+default-features = false
+features = ["ahash", "nightly", "inline-more"]
+optional = true
+
+[dependencies.ahash]
+version = "0.3.2"
+default-features = false
+optional = true
 
 [features]
 #default = ["no_function", "no_index", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize"]
 default = [ "optimize_full" ]
 debug_msgs = []     # print debug messages on function registrations and calls
 unchecked = []      # unchecked arithmetic
-no_stdlib = []      # no standard library of utility functions
+no_stdlib = ["num-traits/libm", "hashbrown", "core-error", "libm"]      # no standard library of utility functions
 no_index = []       # no arrays and indexing
 no_float = []       # no floating-point
 no_function = []    # no script-defined functions
diff --git a/src/any.rs b/src/any.rs
index 96fe099bf..927f2124a 100644
--- a/src/any.rs
+++ b/src/any.rs
@@ -1,7 +1,8 @@
 //! Helper module which defines the `Any` trait to to allow dynamic value handling.
 
-use std::{
-    any::{type_name, TypeId},
+use crate::stdlib::{
+    any::{self, type_name, TypeId},
+    boxed::Box,
     fmt,
 };
 
@@ -12,7 +13,7 @@ pub type Variant = dyn Any;
 pub type Dynamic = Box<Variant>;
 
 /// A trait covering any type.
-pub trait Any: std::any::Any {
+pub trait Any: any::Any {
     /// Get the `TypeId` of this type.
     fn type_id(&self) -> TypeId;
 
@@ -27,7 +28,7 @@ pub trait Any: std::any::Any {
     fn _closed(&self) -> _Private;
 }
 
-impl<T: Clone + std::any::Any + ?Sized> Any for T {
+impl<T: Clone + any::Any + ?Sized> Any for T {
     fn type_id(&self) -> TypeId {
         TypeId::of::<T>()
     }
diff --git a/src/api.rs b/src/api.rs
index 3c248d5b3..172e55d4f 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -12,13 +12,15 @@ use crate::scope::Scope;
 #[cfg(not(feature = "no_optimize"))]
 use crate::optimize::optimize_ast;
 
-use std::{
+use crate::stdlib::{
     any::{type_name, TypeId},
-    fs::File,
-    io::prelude::*,
-    path::PathBuf,
+    boxed::Box,
+    string::{String, ToString},
     sync::Arc,
+    vec::Vec,
 };
+#[cfg(not(feature = "no_stdlib"))]
+use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf};
 
 impl<'e> Engine<'e> {
     pub(crate) fn register_fn_raw(
@@ -115,6 +117,7 @@ impl<'e> Engine<'e> {
         parse(&mut tokens_stream.peekable(), self, scope)
     }
 
+    #[cfg(not(feature = "no_stdlib"))]
     fn read_file(path: PathBuf) -> Result<String, EvalAltResult> {
         let mut f = File::open(path.clone())
             .map_err(|err| EvalAltResult::ErrorReadingScriptFile(path.clone(), err))?;
@@ -127,12 +130,14 @@ impl<'e> Engine<'e> {
     }
 
     /// Compile a file into an AST.
+    #[cfg(not(feature = "no_stdlib"))]
     pub fn compile_file(&self, path: PathBuf) -> Result<AST, EvalAltResult> {
         self.compile_file_with_scope(&Scope::new(), path)
     }
 
     /// Compile a file into an AST using own scope.
     /// The scope is useful for passing constants into the script for optimization.
+    #[cfg(not(feature = "no_stdlib"))]
     pub fn compile_file_with_scope(
         &self,
         scope: &Scope,
@@ -145,11 +150,13 @@ impl<'e> Engine<'e> {
     }
 
     /// Evaluate a file.
+    #[cfg(not(feature = "no_stdlib"))]
     pub fn eval_file<T: Any + Clone>(&mut self, path: PathBuf) -> Result<T, EvalAltResult> {
         Self::read_file(path).and_then(|contents| self.eval::<T>(&contents))
     }
 
     /// Evaluate a file with own scope.
+    #[cfg(not(feature = "no_stdlib"))]
     pub fn eval_file_with_scope<T: Any + Clone>(
         &mut self,
         scope: &mut Scope,
@@ -234,6 +241,7 @@ impl<'e> Engine<'e> {
     ///
     /// Note - 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_stdlib"))]
     pub fn consume_file(
         &mut self,
         retain_functions: bool,
@@ -247,6 +255,7 @@ impl<'e> Engine<'e> {
     ///
     /// Note - 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_stdlib"))]
     pub fn consume_file_with_scope(
         &mut self,
         scope: &mut Scope,
diff --git a/src/builtin.rs b/src/builtin.rs
index b85cf3068..1214c1b0a 100644
--- a/src/builtin.rs
+++ b/src/builtin.rs
@@ -17,9 +17,12 @@ use num_traits::{
     CheckedShr, CheckedSub,
 };
 
-use std::{
+use crate::stdlib::{
+    boxed::Box,
     fmt::{Debug, Display},
+    format,
     ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub},
+    string::{String, ToString},
     {i32, i64, u32},
 };
 
diff --git a/src/call.rs b/src/call.rs
index 2a2822eb9..a4dd7c908 100644
--- a/src/call.rs
+++ b/src/call.rs
@@ -1,6 +1,7 @@
 //! Helper module which defines `FnArgs` to make function calling easier.
 
 use crate::any::{Any, Dynamic};
+use crate::stdlib::{string::String, vec, vec::Vec};
 
 #[cfg(not(feature = "no_index"))]
 use crate::engine::Array;
diff --git a/src/engine.rs b/src/engine.rs
index bee73cff7..9b239e962 100644
--- a/src/engine.rs
+++ b/src/engine.rs
@@ -11,12 +11,17 @@ use crate::optimize::OptimizationLevel;
 #[cfg(not(feature = "no_index"))]
 use crate::INT;
 
-use std::{
+use crate::stdlib::{
     any::{type_name, TypeId},
     borrow::Cow,
+    boxed::Box,
     collections::HashMap,
+    format,
     iter::once,
+    string::{String, ToString},
     sync::Arc,
+    vec,
+    vec::Vec,
 };
 
 /// An dynamic array of `Dynamic` values.
diff --git a/src/error.rs b/src/error.rs
index a582bcb2b..166108c8a 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -1,7 +1,7 @@
 //! Module containing error definitions for the parsing process.
 
 use crate::parser::Position;
-use std::{char, error::Error, fmt};
+use crate::stdlib::{char, error::Error, fmt, string::String};
 
 /// Error when tokenizing the script text.
 #[derive(Debug, Eq, PartialEq, Hash, Clone)]
diff --git a/src/fn_register.rs b/src/fn_register.rs
index 12f150472..d05048a97 100644
--- a/src/fn_register.rs
+++ b/src/fn_register.rs
@@ -4,7 +4,7 @@ use crate::any::{Any, Dynamic};
 use crate::engine::{Engine, FnCallArgs};
 use crate::parser::Position;
 use crate::result::EvalAltResult;
-use std::any::TypeId;
+use crate::stdlib::{any::TypeId, boxed::Box, string::ToString, vec};
 
 /// A trait to register custom functions with the `Engine`.
 ///
diff --git a/src/lib.rs b/src/lib.rs
index ceb343962..1e8aa47cf 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -16,7 +16,7 @@
 //!
 //! And the Rust part:
 //!
-//! ```rust,no_run
+//! ```rust,ignore
 //! use rhai::{Engine, EvalAltResult, RegisterFn};
 //!
 //! fn main() -> Result<(), EvalAltResult>
@@ -37,8 +37,12 @@
 //!
 //! [Check out the README on GitHub for more information!](https://github.com/jonathandturner/rhai)
 
+#![cfg_attr(feature = "no_stdlib", no_std)]
 #![allow(non_snake_case)]
 
+#[cfg(feature = "no_stdlib")]
+extern crate alloc;
+
 // needs to be here, because order matters for macros
 macro_rules! debug_println {
     () => (
@@ -61,6 +65,8 @@ macro_rules! debug_println {
     );
 }
 
+#[macro_use]
+mod stdlib;
 mod any;
 mod api;
 mod builtin;
diff --git a/src/optimize.rs b/src/optimize.rs
index 8a75a905d..b3e110466 100644
--- a/src/optimize.rs
+++ b/src/optimize.rs
@@ -5,7 +5,11 @@ use crate::engine::{Engine, FnCallArgs, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD
 use crate::parser::{map_dynamic_to_expr, Expr, FnDef, Stmt, AST};
 use crate::scope::{Scope, ScopeEntry, VariableType};
 
-use std::sync::Arc;
+use crate::stdlib::{
+    sync::Arc,
+    vec::Vec, string::{String, ToString},
+    boxed::Box, vec,
+};
 
 /// Level of optimization performed
 #[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
diff --git a/src/parser.rs b/src/parser.rs
index 366329c35..a66990b69 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -8,9 +8,19 @@ use crate::scope::{Scope, VariableType};
 #[cfg(not(feature = "no_optimize"))]
 use crate::optimize::optimize_ast;
 
-use std::{
-    borrow::Cow, char, cmp::Ordering, fmt, iter::Peekable, str::Chars, str::FromStr, sync::Arc,
-    usize,
+use crate::stdlib::{
+    borrow::Cow,
+    boxed::Box,
+    char,
+    cmp::Ordering,
+    fmt, format,
+    iter::Peekable,
+    str::Chars,
+    str::FromStr,
+    string::{String, ToString},
+    sync::Arc,
+    usize, vec,
+    vec::Vec,
 };
 
 /// The system integer type.
diff --git a/src/result.rs b/src/result.rs
index faa8db7ce..12eb9d78f 100644
--- a/src/result.rs
+++ b/src/result.rs
@@ -4,7 +4,14 @@ use crate::any::Dynamic;
 use crate::error::ParseError;
 use crate::parser::{Position, INT};
 
-use std::{error::Error, fmt, path::PathBuf};
+use crate::stdlib::{
+    error::Error,
+    fmt,
+    string::{String, ToString},
+};
+
+#[cfg(not(feature = "no_stdlib"))]
+use crate::stdlib::path::PathBuf;
 
 /// Evaluation result.
 ///
@@ -47,6 +54,7 @@ pub enum EvalAltResult {
     /// Wrapped value is the type of the actual result.
     ErrorMismatchOutputType(String, Position),
     /// Error reading from a script file. Wrapped value is the path of the script file.
+    #[cfg(not(feature = "no_stdlib"))]
     ErrorReadingScriptFile(PathBuf, std::io::Error),
     /// Inappropriate member access.
     ErrorDotExpr(String, Position),
@@ -93,6 +101,7 @@ impl EvalAltResult {
             }
             Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable",
             Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect",
+            #[cfg(not(feature = "no_stdlib"))]
             Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file",
             Self::ErrorDotExpr(_, _) => "Malformed dot expression",
             Self::ErrorArithmetic(_, _) => "Arithmetic error",
@@ -127,6 +136,7 @@ impl fmt::Display for EvalAltResult {
             }
             Self::LoopBreak => write!(f, "{}", desc),
             Self::Return(_, pos) => write!(f, "{} ({})", desc, pos),
+            #[cfg(not(feature = "no_stdlib"))]
             Self::ErrorReadingScriptFile(path, err) => {
                 write!(f, "{} '{}': {}", desc, path.display(), err)
             }
@@ -199,7 +209,9 @@ impl<T: AsRef<str>> From<T> for EvalAltResult {
 impl EvalAltResult {
     pub fn position(&self) -> Position {
         match self {
-            Self::ErrorReadingScriptFile(_, _) | Self::LoopBreak => Position::none(),
+            #[cfg(not(feature = "no_stdlib"))]
+            Self::ErrorReadingScriptFile(_, _) => Position::none(),
+            Self::LoopBreak => Position::none(),
 
             Self::ErrorParsing(err) => err.position(),
 
@@ -226,7 +238,9 @@ impl EvalAltResult {
 
     pub(crate) fn set_position(&mut self, new_position: Position) {
         match self {
-            Self::ErrorReadingScriptFile(_, _) | Self::LoopBreak => (),
+            #[cfg(not(feature = "no_stdlib"))]
+            Self::ErrorReadingScriptFile(_, _) => (),
+            Self::LoopBreak => (),
 
             Self::ErrorParsing(ParseError(_, ref mut pos))
             | Self::ErrorFunctionNotFound(_, ref mut pos)
diff --git a/src/scope.rs b/src/scope.rs
index 92c1fe14e..e1bedde68 100644
--- a/src/scope.rs
+++ b/src/scope.rs
@@ -3,7 +3,12 @@
 use crate::any::{Any, Dynamic};
 use crate::parser::{map_dynamic_to_expr, Expr, Position};
 
-use std::borrow::Cow;
+use crate::stdlib::{
+    borrow::Cow,
+    iter,
+    string::{String, ToString},
+    vec::Vec,
+};
 
 /// Type of a variable in the Scope.
 #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
@@ -193,7 +198,7 @@ impl<'a> Scope<'a> {
     }
 }
 
-impl<'a, K> std::iter::Extend<(K, VariableType, Dynamic)> for Scope<'a>
+impl<'a, K> iter::Extend<(K, VariableType, Dynamic)> for Scope<'a>
 where
     K: Into<Cow<'a, str>>,
 {
diff --git a/src/stdlib.rs b/src/stdlib.rs
new file mode 100644
index 000000000..fa147c7da
--- /dev/null
+++ b/src/stdlib.rs
@@ -0,0 +1,23 @@
+#[cfg(feature = "no_stdlib")]
+mod inner {
+    pub use core::{
+        any, arch, array, ascii, cell, char, clone, cmp, convert, default, f32, f64, ffi, fmt,
+        future, hash, hint, i128, i16, i32, i64, i8, isize, iter, marker, mem, num, ops, option,
+        panic, pin, prelude, ptr, result, slice, str, task, time, u128, u16, u32, u64, u8, usize,
+    };
+
+    pub use alloc::{borrow, boxed, format, string, sync, vec};
+
+    pub use core_error as error;
+
+    pub mod collections {
+        pub use hashbrown::HashMap;
+    }
+}
+
+#[cfg(not(feature = "no_stdlib"))]
+mod inner {
+    pub use std::*;
+}
+
+pub use self::inner::*;


Test patch: