Skip to content

Commit 40113bd

Browse files
authored
Merge pull request #373 from dtolnay/nostd
Allow disabling std dependency on 1.81+
2 parents 8277ec4 + d8ed5fb commit 40113bd

File tree

10 files changed

+146
-9
lines changed

10 files changed

+146
-9
lines changed

.github/workflows/ci.yml

+4-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
strategy:
2525
fail-fast: false
2626
matrix:
27-
rust: [nightly, beta, stable, 1.70.0]
27+
rust: [nightly, beta, stable, 1.81.0, 1.70.0]
2828
timeout-minutes: 45
2929
steps:
3030
- uses: actions/checkout@v4
@@ -38,7 +38,9 @@ jobs:
3838
- name: Enable nightly-only tests
3939
run: echo RUSTFLAGS=${RUSTFLAGS}\ --cfg=thiserror_nightly_testing >> $GITHUB_ENV
4040
if: matrix.rust == 'nightly'
41-
- run: cargo test --all
41+
- run: cargo test --workspace --exclude thiserror_no_std_test
42+
- run: cargo test --manifest-path tests/no-std/Cargo.toml
43+
if: matrix.rust != '1.70.0'
4244
- uses: actions/upload-artifact@v4
4345
if: matrix.rust == 'nightly' && always()
4446
with:

Cargo.toml

+17-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,22 @@ license = "MIT OR Apache-2.0"
1111
repository = "https://github.com/dtolnay/thiserror"
1212
rust-version = "1.61"
1313

14+
[features]
15+
default = ["std"]
16+
17+
# Std feature enables support for formatting std::path::{Path, PathBuf}
18+
# conveniently in an error message.
19+
#
20+
# #[derive(Error, Debug)]
21+
# #[error("failed to create configuration file {path}")]
22+
# pub struct MyError {
23+
# pub path: PathBuf,
24+
# pub source: std::io::Error,
25+
# }
26+
#
27+
# Without std, this would need to be written #[error("... {}", path.display())].
28+
std = []
29+
1430
[dependencies]
1531
thiserror-impl = { version = "=1.0.68", path = "impl" }
1632

@@ -21,7 +37,7 @@ rustversion = "1.0.13"
2137
trybuild = { version = "1.0.81", features = ["diff"] }
2238

2339
[workspace]
24-
members = ["impl"]
40+
members = ["impl", "tests/no-std"]
2541

2642
[package.metadata.docs.rs]
2743
targets = ["x86_64-unknown-linux-gnu"]

build.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,14 @@ fn main() {
5757
println!("cargo:rerun-if-env-changed=RUSTC_BOOTSTRAP");
5858
}
5959

60-
let rustc = match rustc_minor_version() {
60+
// core::error::Error stabilized in Rust 1.81
61+
// https://blog.rust-lang.org/2024/09/05/Rust-1.81.0.html#coreerrorerror
62+
let rustc = rustc_minor_version();
63+
if cfg!(not(feature = "std")) && rustc.map_or(false, |rustc| rustc < 81) {
64+
println!("cargo:rustc-cfg=feature=\"std\"");
65+
}
66+
67+
let rustc = match rustc {
6168
Some(rustc) => rustc,
6269
None => return,
6370
};

build/probe.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// member access API. If the current toolchain is able to compile it, then
33
// thiserror is able to provide backtrace support.
44

5+
#![no_std]
56
#![feature(error_generic_member_access)]
67

78
use core::error::{Error, Request};

src/aserror.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
use core::error::Error;
12
use core::panic::UnwindSafe;
2-
use std::error::Error;
33

44
#[doc(hidden)]
55
pub trait AsDynError<'a>: Sealed {

src/display.rs

+35
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use core::fmt::Display;
2+
#[cfg(feature = "std")]
23
use std::path::{self, Path, PathBuf};
34

45
#[doc(hidden)]
@@ -21,6 +22,7 @@ where
2122
}
2223
}
2324

25+
#[cfg(feature = "std")]
2426
impl<'a> AsDisplay<'a> for Path {
2527
type Target = path::Display<'a>;
2628

@@ -30,6 +32,7 @@ impl<'a> AsDisplay<'a> for Path {
3032
}
3133
}
3234

35+
#[cfg(feature = "std")]
3336
impl<'a> AsDisplay<'a> for PathBuf {
3437
type Target = path::Display<'a>;
3538

@@ -42,5 +45,37 @@ impl<'a> AsDisplay<'a> for PathBuf {
4245
#[doc(hidden)]
4346
pub trait Sealed {}
4447
impl<T: Display> Sealed for &T {}
48+
#[cfg(feature = "std")]
4549
impl Sealed for Path {}
50+
#[cfg(feature = "std")]
4651
impl Sealed for PathBuf {}
52+
53+
// Add a synthetic second impl of AsDisplay to prevent the "single applicable
54+
// impl" rule from making too weird inference decision based on the single impl
55+
// for &T, which could lead to code that compiles with thiserror's std feature
56+
// off but breaks under feature unification when std is turned on by an
57+
// unrelated crate.
58+
#[cfg(not(feature = "std"))]
59+
mod placeholder {
60+
use super::{AsDisplay, Sealed};
61+
use core::fmt::{self, Display};
62+
63+
pub struct Placeholder;
64+
65+
impl<'a> AsDisplay<'a> for Placeholder {
66+
type Target = Self;
67+
68+
#[inline]
69+
fn as_display(&'a self) -> Self::Target {
70+
Placeholder
71+
}
72+
}
73+
74+
impl Display for Placeholder {
75+
fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
76+
unreachable!()
77+
}
78+
}
79+
80+
impl Sealed for Placeholder {}
81+
}

src/lib.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@
258258
//!
259259
//! [`anyhow`]: https://github.com/dtolnay/anyhow
260260
261+
#![no_std]
261262
#![doc(html_root_url = "https://docs.rs/thiserror/1.0.68")]
262263
#![allow(
263264
clippy::module_name_repetitions,
@@ -270,6 +271,11 @@
270271
#[cfg(all(thiserror_nightly_testing, not(error_generic_member_access)))]
271272
compile_error!("Build script probe failed to compile.");
272273

274+
#[cfg(feature = "std")]
275+
extern crate std;
276+
#[cfg(feature = "std")]
277+
extern crate std as core;
278+
273279
mod aserror;
274280
mod display;
275281
#[cfg(error_generic_member_access)]
@@ -287,9 +293,9 @@ pub mod __private {
287293
#[cfg(error_generic_member_access)]
288294
#[doc(hidden)]
289295
pub use crate::provide::ThiserrorProvide;
290-
#[cfg(not(thiserror_no_backtrace_type))]
291296
#[doc(hidden)]
292-
pub use std::backtrace::Backtrace;
297+
pub use core::error::Error;
298+
#[cfg(all(feature = "std", not(thiserror_no_backtrace_type)))]
293299
#[doc(hidden)]
294-
pub use std::error::Error;
300+
pub use std::backtrace::Backtrace;
295301
}

src/provide.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::error::{Error, Request};
1+
use core::error::{Error, Request};
22

33
#[doc(hidden)]
44
pub trait ThiserrorProvide: Sealed {

tests/no-std/Cargo.toml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "thiserror_no_std_test"
3+
version = "0.0.0"
4+
authors = ["David Tolnay <dtolnay@gmail.com>"]
5+
edition = "2021"
6+
publish = false
7+
8+
[lib]
9+
path = "test.rs"
10+
11+
[dependencies]
12+
thiserror = { path = "../..", default-features = false }

tests/no-std/test.rs

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#![no_std]
2+
3+
use thiserror::Error;
4+
5+
#[derive(Error, Debug)]
6+
pub enum Error {
7+
#[error("Error::E")]
8+
E(#[from] SourceError),
9+
}
10+
11+
#[derive(Error, Debug)]
12+
#[error("SourceError {field}")]
13+
pub struct SourceError {
14+
pub field: i32,
15+
}
16+
17+
#[cfg(test)]
18+
mod tests {
19+
use crate::{Error, SourceError};
20+
use core::error::Error as _;
21+
use core::fmt::{self, Write};
22+
use core::mem;
23+
24+
struct Buf<'a>(&'a mut [u8]);
25+
26+
impl Write for Buf<'_> {
27+
fn write_str(&mut self, s: &str) -> fmt::Result {
28+
if s.len() <= self.0.len() {
29+
let (out, rest) = mem::take(&mut self.0).split_at_mut(s.len());
30+
out.copy_from_slice(s.as_bytes());
31+
self.0 = rest;
32+
Ok(())
33+
} else {
34+
Err(fmt::Error)
35+
}
36+
}
37+
}
38+
39+
#[test]
40+
fn test() {
41+
let source = SourceError { field: -1 };
42+
let error = Error::from(source);
43+
44+
let source = error
45+
.source()
46+
.unwrap()
47+
.downcast_ref::<SourceError>()
48+
.unwrap();
49+
50+
let mut msg = [b'~'; 17];
51+
write!(Buf(&mut msg), "{error}").unwrap();
52+
assert_eq!(msg, *b"Error::E~~~~~~~~~");
53+
54+
let mut msg = [b'~'; 17];
55+
write!(Buf(&mut msg), "{source}").unwrap();
56+
assert_eq!(msg, *b"SourceError -1~~~");
57+
}
58+
}

0 commit comments

Comments
 (0)