Apart: First, use Git to create a brand new department to your challenge. That means, if issues don’t work out, you’ll be able to simply undo all adjustments.
Mark the highest of lib.rs
with:
#![cfg_attr(not(test), no_std)]
This tells the Rust compiler to not embody the usual library, besides when testing.
Apart 1: My challenge is a library challenge with a
lib.rs
. I consider the steps for a binary challenge with aimportant.rs
are about the identical, however I haven’t examined them.Apart 2: We’ll speak rather more about code testing in later guidelines.
Including the “no_std” line to range-set-blaze
’s lib.rs
, causes 40 compiler issues, most of this kind:
Repair a few of these by altering, “std::” to “core::” in your important code (not in take a look at code). For range-set-blaze
, this reduces the variety of issues from 40 to 12. This repair helps as a result of many gadgets, akin to std::cmp::max
, are additionally out there as core::cmp::max
.
Sadly, gadgets akin to Vec
and Field
can’t be in core
as a result of they should allocate reminiscence. Fortunately, for those who’re keen to help reminiscence allocation, you’ll be able to nonetheless use them.
Must you enable your crate to allocate reminiscence? For WASM you must. For a lot of embedded purposes, you additionally ought to. For some embedded purposes, nonetheless, you shouldn’t. In the event you determine to permit reminiscence allocation, then on the high of lib.rs
add:
extern crate alloc;
Now you can add traces akin to these to get entry to many memory-allocated gadgets:
extern crate alloc;use alloc::boxed::Field;
use alloc::collections::btree_map;
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use alloc::{format, string::String};
use alloc::vec;
With range-set-blaze
, this reduces the variety of issues from 12 to 2. We’ll repair these in Rule 3.
Apart: What in case you are writing for an embedded atmosphere that may’t use reminiscence allocation and are having issues with, for instance,
Vec
. You could possibly re-write. For instance, you could possibly use an array instead of a vector. If that doesn’t work, check out the opposite guidelines. If nothing works, chances are you’ll not have the ability to port your crate tono_std
.
The Rust compiler complains in case your challenge used a crate that places “std” features in your code. Typically, you’ll be able to search crates.io and discover various “no_std” crates. For instance, the favored thiserror
crate injects “std” into your code. Nonetheless, the neighborhood has created alternatives that do not.
Within the case of range-set-blaze
, the 2 remaining issues relate to crate gen_ops
— a beautiful crate for outlining operators akin to “+” and “&” conveniently. Model 0.3.0 of gen_ops
didn’t totally help “no std”. Model 0.4.0, nonetheless, does. I up to date my dependencies in Cargo.toml
and improved my “no std” compatibility.
I can now run these instructions:
cargo test # test that compiles as no_std
cargo take a look at # test that checks, utilizing std, nonetheless go
The command cargo test
confirms that my crate isn’t immediately utilizing the usual library. The command cargo take a look at
confirms that my checks (which nonetheless use the usual library) proceed to go. In case your crate nonetheless doesn’t compile, check out the subsequent rule.
Embedded processors typically don’t help studying and writing information. Likewise, WASM doesn’t but totally help information. Whereas you could find some file-related “no std” crates, none appear complete. So, if file IO is central to your crate, porting to WASM and embedded is probably not sensible.
Nonetheless, if file IO — or every other std-only perform — is merely incidental to your crate, you can also make that perform optionally available by way of a “std” characteristic. Right here is how:
Add this part to your Cargo.toml
:
[package]
#...
resolver = "2" # the default for Rust 2021+[features]
default = ["std"]
std = []
alloc = []
This says that your crate now has two options, “std” and “alloc”. By default, the compiler ought to use “std”.
On the high of your lib.rs
, exchange:
#![cfg_attr(not(test), no_std)]
with:
#![cfg_attr(not(feature = "std"), no_std)]
This says that if you don’t apply the “std” characteristic, the compiler ought to compile with out the usual library.
On the road earlier than any code that’s std-only, positioned #[cfg(feature = "std")]
. For instance, right here we outline a perform that creates a RangeSetBlaze
struct primarily based on the contents of a file:
#[cfg(feature = "std")]
use std::fs::File;
#[cfg(feature = "std")]
use std::io::{self, BufRead};
#[cfg(feature = "std")]
use std::path::Path;#[cfg(feature = "std")]
#[allow(missing_docs)]
pub fn demo_read_ranges_from_file<P, T>(path: P) -> io::Outcome<RangeSetBlaze<T>>
the place
P: AsRef<Path>,
T: FromStr + Integer,
{
//...code not proven
}
To test the “std” and “alloc” options, do that:
cargo test # std
cargo test --features alloc --no-default-features
We are able to take a look at “std” with
cargo take a look at
Apart: Surprisingly,
cargo take a look at --features alloc --no-default-features
doesn’t take a look at « alloc”. That’s as a result of checks require threads, allocation, and other things that is probably not out there inno_std
so cargo all the time runs common checks as “std”.
At this level we’re checking each « std” and “alloc”, so can we assume that our library will work with WASM and embedded. No! Typically, Nothing works with out being examined. Particularly, we may be relying on crates that use “std” code internally. To seek out these points, we should take a look at within the WASM and embedded environments.
Set up the WASM cross compiler and test your challenge with these instructions:
rustup goal add wasm32-unknown-unknown # solely want to do that as soon as
# might discover points
cargo test --target wasm32-unknown-unknown --features alloc --no-default-features
Once I do that on range-set-blaze
, it complains that the getrandom
crate doesn’t work with WASM. On the one hand, I’m not shocked that WASM doesn’t totally help random numbers. However, I’m shocked as a result of my challenge doesn’t immediately depend upon getrandom
. To seek out the oblique dependency, I take advantage of cargo tree
. I uncover that my challenge depends upon crate rand
which depends upon getrandom
. Right here is the cargo tree
command to make use of:
cargo tree --edges no-dev --format "{p} {f}" --features alloc --no-default-features
The command outputs each crates and the options they use:
range-set-blaze v0.1.6 (O:ProjectsSciencewasmetcwasm3) alloc
├── gen_ops v0.4.0
├── itertools v0.10.5 default,use_alloc,use_std
│ └── both v1.8.1 use_std
├── num-integer v0.1.45 default,std
│ └── num-traits v0.2.15 default,std
│ [build-dependencies]
│ └── autocfg v1.1.0
│ [build-dependencies]
│ └── autocfg v1.1.0
├── num-traits v0.2.15 default,std (*)
├── rand v0.8.5 alloc,default,getrandom,libc,rand_chacha,std,std_rng
│ ├── rand_chacha v0.3.1 std
│ │ ├── ppv-lite86 v0.2.17 simd,std
│ │ └── rand_core v0.6.4 alloc,getrandom,std
│ │ └── getrandom v0.2.9 std
│ │ └── cfg-if v1.0.0
...
The output reveals that range-set-blaze
depends upon rand
. Additionally, it reveals that rand
depends upon getrandom
with its “std” characteristic.
I learn the getrandom
documentation and be taught that its “js” characteristic helps WASM. So, how can we inform rand
to make use of getrandom/js
, however solely after we compile with our « alloc” characteristic? We replace our Cargo.toml
like so:
[features]
default = ["std"]
std = ["getrandom/std"]
alloc = ["getrandom/js"][dependencies]
# ...
getrandom = "0.2.10"
This says that our “std” characteristic depends upon getrandom
’s “std » characteristic. Our “alloc” characteristic, nonetheless, ought to use the js
characteristic of getrandom
.
This now works:
cargo test --target wasm32-unknown-unknown --features alloc --no-default-features
So, we now have WASM compiling, however what about testing WASM?
Let’s put the WASM model to work, first with checks after which with a demo internet web page.
Create WASM checks in checks/wasm.rs
You may take a look at on WASM virtually as simply as you’ll be able to take a look at natively. We do that by having the unique checks solely run natively whereas an virtually duplicate set of checks run on WASM. Listed here are the steps primarily based on The wasm-bindgen
Guide:
- Do
cargo set up wasm-bindgen-cli
- Copy your present integration checks from, for instance,
checks/integration_tests.rs
tochecks/wasm.rs
. (Recall that in Rust, integration checks are checks that stay outdoors thesrc
listing and that see solely the general public strategies of a challenge.) - On the high of
checks/wasm.rs
, take away#![cfg(test)]
and add#![cfg(target_arch = “wasm32”)]
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser); - In
wasm.rs
, exchange all#[test]
’s to#[wasm_bindgen_test]
’s. - In every single place you could have
#![cfg(test)]
(sometimes, inchecks/integration_tests.rs
andsrc/checks.rs
) add the extra line:#![cfg(not(target_arch = "wasm32"))]
- In your,
Cargo.toml
, change your[dev-dependencies]
(if any) to[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
- In your,
Cargo.toml
, add a bit:
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = "0.3.37"
With all this arrange, native checks, cargo take a look at
, ought to nonetheless work. In the event you don’t have the Chrome browser put in, set up it. Now attempt to run the WASM checks with:
wasm-pack take a look at --chrome --headless --features alloc --no-default-features
It’s going to seemingly fail as a result of your WASM checks use dependencies that haven’t or can’t be put in Cargo.toml
. Undergo every concern and both:
- Add the wanted dependencies to
Cargo.toml’
s[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
part, or - Take away the checks from
checks/wasm.rs
.
For range-set-blaze
, I eliminated all WASM checks associated to testing the bundle’s benchmarking framework. These checks will nonetheless be run on the native facet. Some helpful checks in testswasm.rs
wanted crate syntactic-for
, so I added it to Cargo.toml
, below [target.'cfg(target_arch = "wasm32")'.dev-dependencies]
. With this fastened, all 59 WASM checks run and go.
Apart: In case your challenge contains an examples folder, chances are you’ll want create a
native
module inside your instance and awasm
module. See thisrange-set-blaze
file for an “example” example of how to do that.
Create a WASM demo in checks/wasm-demo
A part of the enjoyable of supporting WASM is that you may demo your Rust code in an online web page. Here’s a internet demo of range-set-blaze
.
Comply with these steps to create your individual internet demo:
In your challenge’s important Cargo.toml
file, outline a workspace and add checks/wasm-demo
to it:
[workspace]
members = [".", "tests/wasm-demo"]
In your checks folder, create a take a look at/wasm-demo
subfolder.
It ought to comprise a brand new Cargo.toml
like this (change range-set-blaze
to the title of your challenge):
[package]
title = "wasm-demo"
model = "0.1.0"
version = "2021"[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
range-set-blaze = { path = "../..", options = ["alloc"], default-features = false}
Additionally, create a file checks/wasm-demo/src/lib.rs
. Right here is mine:
#![no_std]
extern crate alloc;
use alloc::{string::ToString, vec::Vec};
use range_set_blaze::RangeSetBlaze;
use wasm_bindgen::prelude::*;#[wasm_bindgen]
pub fn disjoint_intervals(enter: Vec<i32>) -> JsValue {
let set: RangeSetBlaze<_> = enter.into_iter().accumulate();
let s = set.to_string();
JsValue::from_str(&s)
}
This file defines a perform known as disjoint_intervals
that takes a vector of integers as enter, for instance, 100,103,101,102,-3,-4
. Utilizing the range-set-blaze
bundle, the perform returns a string of the integers as sorted, disjoint ranges, for instance, -4..=-3, 100..=103
.
As your closing step, create file checks/wasm-demo/index.html.
Mine makes use of a bit JavaScript to simply accept an inventory of integers after which name the Rust WASM perform disjoint_intervals
.
<!DOCTYPE html>
<html>
<physique>
<h2>Rust WASM RangeSetBlaze Demo</h2>
<p>Enter an inventory of comma-separated integers:</p>
<enter id="inputData" kind="textual content" worth="100,103,101,102,-3,-4" oninput="callWasmFunction()">
<br><br>
<p id="output"></p>
<script kind="module">
import init, { disjoint_intervals } from './pkg/wasm_demo.js';perform callWasmFunction() {
let inputData = doc.getElementById("inputData").worth;
let knowledge = inputData.break up(',').map(x => x.trim() === "" ? NaN : Quantity(x)).filter(n => !isNaN(n));
const typedArray = Int32Array.from(knowledge);
let outcome = disjoint_intervals(typedArray);
doc.getElementById("output").innerHTML = outcome;
}
window.callWasmFunction = callWasmFunction;
init().then(callWasmFunction);
</script>
</physique>
</html>
To run the demo domestically, first transfer your terminal to checks/wasm-demo
. Then do:
# from checks/wasm-demo
wasm-pack construct --target internet
Subsequent, begin an area internet server and think about the web page. I take advantage of the Live Preview extension to VS Code. Many individuals use python -m http.server
. The range-set-blaze
demo appears like this (additionally out there, live on GitHub):
I discover watching my Rust challenge run in an online web page very gratifying. If WASM-compatibility is all you’re in search of, you’ll be able to skip to Rule 9.
If you wish to take your challenge a step past WASM, comply with this rule and the subsequent.
Make certain you progress your terminal again to your challenge’s dwelling listing. Then, set up thumbv7m-none-eabi
, a well-liked embedded processor, and test your challenge with these instructions:
# from challenge's dwelling listing
rustup goal add thumbv7m-none-eabi # solely want to do that as soon as
# will seemingly discover points
cargo test --target thumbv7m-none-eabi --features alloc --no-default-features
Once I do that on range-set-blaze
, I get errors associated to 4 units of dependencies:
thiserror
— My challenge relied on this crate however didn’t truly use it. I eliminated the dependency.rand
andgetrandom
— My challenge solely wants random numbers for native testing, so I moved the dependency to[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
. I additionally up to date my important and testing code.itertools
,num-traits
, andnum-integer
— These crates supply options for “std” and “alloc”. I up to dateCargo.toml
like so:
...
[features]
default = ["std"]
std = ["itertools/use_std", "num-traits/std", "num-integer/std"]
alloc = ["itertools/use_alloc", "num-traits", "num-integer"][dependencies]
itertools = { model = "0.10.1", optionally available = true, default-features = false }
num-integer = { model = "0.1.44", optionally available = true, default-features = false }
num-traits = { model = "0.2.15", optionally available = true, default-features = false }
gen_ops = "0.4.0"
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
#...
rand = "0.8.4"
#...
How did I do know which characteristic of which dependancies to make use of? Understanding the options of a crate akin to itertools
requires studying its documentation and (typically) going to its GitHub repository and studying its Cargo.toml
. You also needs to use cargo tree
to test that you’re getting the will characteristic from every dependency. For instance, this use of cargo tree
reveals that for a default compile, I get the “std” options of range-set-blaze
, num-integer
, and num-traits
and the “use-std” options of itertools
and both:
cargo tree --edges no-dev --format "{p} {f}"
range-set-blaze v0.1.6 (O:ProjectsSciencewasmetcwasm4) default,itertools,num-integer,num-traits,std
├── gen_ops v0.4.0
├── itertools v0.10.5 use_alloc,use_std
│ └── both v1.8.1 use_std
├── num-integer v0.1.45 std
│ └── num-traits v0.2.15 std
│ [build-dependencies]
│ └── autocfg v1.1.0
│ [build-dependencies]
│ └── autocfg v1.1.0
└── num-traits v0.2.15 std (*)
And this reveals that for a --features alloc --no-default-feature
compile, I get the specified “use_alloc” characteristic of itertools
and “no default” model of the opposite dependances:
cargo tree --edges no-dev --format "{p} {f}" --features alloc --no-default-features
range-set-blaze v0.1.6 (O:ProjectsSciencewasmetcwasm4) alloc,itertools,num-integer,num-traits
├── gen_ops v0.4.0
├── itertools v0.10.5 use_alloc
│ └── both v1.8.1
├── num-integer v0.1.45
│ └── num-traits v0.2.15
│ [build-dependencies]
│ └── autocfg v1.1.0
│ [build-dependencies]
│ └── autocfg v1.1.0
└── num-traits v0.2.15 (*)
Once you suppose you could have all the things working, use these instructions to test/take a look at native, WASM, and embedded:
# take a look at native
cargo take a look at
cargo take a look at --features alloc --no-default-features
# test and take a look at WASM
cargo test --target wasm32-unknown-unknown --features alloc --no-default-features
wasm-pack take a look at --chrome --headless --features alloc --no-default-features
# test embedded
cargo test --target thumbv7m-none-eabi --features alloc --no-default-features
These test embedded, however what about testing embedded?
Let’s put our embedded characteristic to work by making a mixed take a look at and demo. We’ll run it on an emulator known as QEMU.
Testing native Rust is straightforward. Testing WASM Rust is OK. Testing embedded Rust is tough. We’ll do solely a single, easy take a look at.
Apart 1: For extra, about operating and emulating embedded Rust see: The Embedded Rust Book.
Apart 2: For concepts on a extra full take a look at framework for embedded Rust, see defmt-test. Sadly, I couldn’t work out the right way to get it to run below emulation. The cortex-m/testsuite challenge makes use of a fork of defmt-test and may run below emulation however doesn’t supply a stand-alone testing crate and requires three further (sub)initiatives.
Apart 3: One embedded take a look at is infinitely higher than no checks. We’ll do the remainder of our testing on the native and WASM degree.
We’ll create the embedded take a look at and demo inside our present checks
folder. The information shall be:
checks/embedded
├── .cargo
│ └── config.toml
├── Cargo.toml
├── construct.rs
├── reminiscence.x
└── src
└── important.rs
Listed here are the steps to creating the information and setting issues up.
- Set up the QEMU emulator. On Home windows, this entails operating an installer after which manually including
"C:Program Filesqemu"
to your path.
2. Create a checks/embedded/Cargo.toml
that depends upon your native challenge with “no default options” and “alloc”. Right here is mine:
[package]
version = "2021"
title = "embedded"
model = "0.1.0"[dependencies]
alloc-cortex-m = "0.4.4"
cortex-m = "0.6.0"
cortex-m-rt = "0.6.10"
cortex-m-semihosting = "0.3.3"
panic-halt = "0.2.0"# reference your native challenge right here
range-set-blaze = { path = "../..", options = ["alloc"], default-features = false }
[[bin]]
title = "embedded"
take a look at = false
bench = false
3. Create a file checks/embedded/src/important.rs
. Put your take a look at code after the “take a look at goes right here” remark. Right here is my file:
// primarily based on https://github.com/rust-embedded/cortex-m-quickstart/blob/grasp/examples/allocator.rs
// and https://github.com/rust-lang/rust/points/51540
#![feature(alloc_error_handler)]
#![no_main]
#![no_std]extern crate alloc;
use alloc::string::ToString;
use alloc_cortex_m::CortexMHeap;
use core::{alloc::Format, iter::FromIterator};
use cortex_m::asm;
use cortex_m_rt::entry;
use cortex_m_semihosting::{debug, hprintln};
use panic_halt as _;
use range_set_blaze::RangeSetBlaze;
#[global_allocator]
static ALLOCATOR: CortexMHeap = CortexMHeap::empty();
const HEAP_SIZE: usize = 1024; // in bytes
#[entry]
fn important() -> ! {
unsafe { ALLOCATOR.init(cortex_m_rt::heap_start() as usize, HEAP_SIZE) }
// take a look at goes right here
let range_set_blaze = RangeSetBlaze::from_iter([100, 103, 101, 102, -3, -4]);
assert!(range_set_blaze.to_string() == "-4..=-3, 100..=103");
hprintln!("{:?}", range_set_blaze.to_string()).unwrap();
// exit QEMU/ NOTE don't run this on {hardware}; it could possibly corrupt OpenOCD state
debug::exit(debug::EXIT_SUCCESS);
loop {}
}
#[alloc_error_handler]
fn alloc_error(_layout: Format) -> ! {
asm::bkpt();
loop {}
}
4. Copy construct.rs
and reminiscence.x
from cortex-m-quickstart
’s GitHub to checks/embedded/
.
5. Create a checks/embedded/.cargo/config.toml
containing:
[target.thumbv7m-none-eabi]
runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config allow=on,goal=native -kernel"[build]
goal = "thumbv7m-none-eabi"
6. Replace your challenge’s important Cargo.toml
by including checks/embedded
to your workspace:
[workspace]
members = [".", "tests/wasm-demo", "tests/embedded"]