├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src └── lib.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: rust 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | # The regular build; we run tests on each feature combination using `cargo hack` 10 | check: 11 | name: Test 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout sources 15 | uses: actions/checkout@v3 16 | 17 | - name: Install cargo-hack 18 | run: cargo install cargo-hack 19 | 20 | - name: Powerset 21 | run: cargo hack test --feature-powerset 22 | 23 | # The embedded build; we don't test here, just run a `cargo check` with no 24 | # dev dependencies. It's common to use the standard library in tests to make 25 | # them easier to write, so we just ensure a "real" downstream build will work 26 | # without the standard library 27 | embedded: 28 | name: Build (embedded) 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout sources 32 | uses: actions/checkout@v3 33 | 34 | - name: Install Rust toolchain 35 | run: | 36 | rustup update nightly && 37 | rustup default nightly && 38 | rustup target add thumbv6m-none-eabi 39 | 40 | - name: Install cargo-hack 41 | run: cargo install cargo-hack 42 | 43 | - name: Powerset 44 | run: cargo hack check --each-feature --exclude-features std -Z avoid-dev-deps --target thumbv6m-none-eabi 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-no-std" 3 | version = "0.0.0" 4 | authors = ["Ashley Mannix "] 5 | publish = false 6 | edition = "2021" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [features] 11 | # enable support for the standard library 12 | std = [] 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ashley Mannix 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Building your no-std Rust library for a no-std target in CI [![rust](https://github.com/KodrAus/rust-no-std/actions/workflows/rust.yml/badge.svg)](https://github.com/KodrAus/rust-no-std/actions/workflows/rust.yml) 2 | 3 | This example demonstrates how to make sure your Rust library will actually build for a no-std target in CI. 4 | 5 | ----- 6 | 7 | The current design of Rust's standard library is split into a few layers, each building on the assumed platform capabilities of the one below. There's: 8 | 9 | - [`std`](https://doc.rust-lang.org/std): the full standard library assumes the presence of threads, a filesystem, and networking. Targets for common operating systems, like `x86_64-pc-windows-msvc`, are supported by `std`. Some targets, like `wasm32-unknown-unknown` are supported by `std`, but some features are shimmed. 10 | - [`alloc`](https://doc.rust-lang.org/alloc): the collections layer builds on the core by assuming runtime support for dynamic memory allocation. 11 | - [`core`](https://doc.rust-lang.org/core): the core layer makes no (well, not very many) assumptions about the underlying platform. Just about any target that can run Rust code is supported by `core`. 12 | 13 | So when you're designing your library you can make it maximally portable by targeting the lowest layer of the standard library that you can. That can become tricky if your library has dependencies though. You might want to target `core`, but a dependency might quietly pull in `std`. If you develop on a platform that supports `std` (which you probably are) then you might not even notice things don't work until a user reports a bug. 14 | 15 | All is not lost though. We can check our libraries against a no-std target during CI so a regression in target support gets caught sooner rather than later. In this example, we do that by building for the `thumbv6m-none-eabi` target, which is only supported by `core`. 16 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Declaring our library as `no-std` unconditionally lets us be consistent 2 | // in how we `use` items from `std` or `core` 3 | #![no_std] 4 | 5 | // We always pull in `std` during tests, because it's just easier 6 | // to write tests when you can assume you're on a capable platform 7 | #[cfg(any(feature = "std", test))] 8 | #[macro_use] 9 | extern crate std; 10 | 11 | // When we're building for a no-std target, we pull in `core`, but alias 12 | // it as `std` so the `use` statements are the same between `std` and `core`. 13 | #[cfg(all(not(feature = "std"), not(test)))] 14 | #[macro_use] 15 | extern crate core as std; 16 | 17 | use crate::std::fmt; 18 | 19 | pub fn write(mut w: impl fmt::Write, v: impl fmt::Debug) -> fmt::Result { 20 | write!(w, "{:?}", v) 21 | } 22 | 23 | // When we are building with `std` there may be additional features 24 | // our library supports. 25 | #[cfg(feature = "std")] 26 | mod std_support { 27 | use crate::{ 28 | std::{ 29 | string::String, 30 | fmt, 31 | }, 32 | write, 33 | }; 34 | 35 | pub fn to_string(v: impl fmt::Debug) -> String { 36 | let mut buf = String::new(); 37 | write(&mut buf, v).expect("failed to write to string"); 38 | 39 | buf 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use super::*; 45 | 46 | #[test] 47 | fn it_works() { 48 | assert_eq!("42", to_string(42)); 49 | } 50 | } 51 | } 52 | 53 | #[cfg(feature = "std")] 54 | pub use self::std_support::*; 55 | 56 | #[cfg(test)] 57 | mod tests { 58 | use super::*; 59 | 60 | use crate::std::string::String; 61 | 62 | #[test] 63 | fn it_works() { 64 | let mut buf = String::new(); 65 | write(&mut buf, 42).expect("failed to write to string"); 66 | 67 | assert_eq!("42", buf); 68 | } 69 | } 70 | --------------------------------------------------------------------------------