Skip to content

Commit

Permalink
Allow disabling std dependency on 1.81+
Browse files Browse the repository at this point in the history
  • Loading branch information
dtolnay committed Nov 6, 2024
1 parent 8277ec4 commit 9df02d6
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 9 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
strategy:
fail-fast: false
matrix:
rust: [nightly, beta, stable, 1.70.0]
rust: [nightly, beta, stable, 1.81.0, 1.70.0]
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
Expand All @@ -38,7 +38,9 @@ jobs:
- name: Enable nightly-only tests
run: echo RUSTFLAGS=${RUSTFLAGS}\ --cfg=thiserror_nightly_testing >> $GITHUB_ENV
if: matrix.rust == 'nightly'
- run: cargo test --all
- run: cargo test --workspace --exclude thiserror_no_std_test
- run: cargo test --manifest-path tests/crate/Cargo.toml --no-default-features
if: matrix.rust != '1.70.0'
- uses: actions/upload-artifact@v4
if: matrix.rust == 'nightly' && always()
with:
Expand Down
18 changes: 17 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,22 @@ license = "MIT OR Apache-2.0"
repository = "https://github.com/dtolnay/thiserror"
rust-version = "1.61"

[features]
default = ["std"]

# Std feature enables support for formatting std::path::{Path, PathBuf}
# conveniently in an error message.
#
# #[derive(Error, Debug)]
# #[error("failed to create configuration file {path}")]
# pub struct MyError {
# pub path: PathBuf,
# pub source: std::io::Error,
# }
#
# Without std, this would need to be written #[error("... {}", path.display())].
std = []

[dependencies]
thiserror-impl = { version = "=1.0.68", path = "impl" }

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

[workspace]
members = ["impl"]
members = ["impl", "tests/no-std"]

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
Expand Down
9 changes: 8 additions & 1 deletion build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,14 @@ fn main() {
println!("cargo:rerun-if-env-changed=RUSTC_BOOTSTRAP");
}

let rustc = match rustc_minor_version() {
// core::error::Error stabilized in Rust 1.81
// https://blog.rust-lang.org/2024/09/05/Rust-1.81.0.html#coreerrorerror
let rustc = rustc_minor_version();
if cfg!(not(feature = "std")) && rustc.map_or(false, |rustc| rustc < 81) {
println!("cargo:rustc-cfg=feature=\"std\"");
}

let rustc = match rustc {
Some(rustc) => rustc,
None => return,
};
Expand Down
1 change: 1 addition & 0 deletions build/probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// member access API. If the current toolchain is able to compile it, then
// thiserror is able to provide backtrace support.

#![no_std]
#![feature(error_generic_member_access)]

use core::error::{Error, Request};
Expand Down
2 changes: 1 addition & 1 deletion src/aserror.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use core::error::Error;
use core::panic::UnwindSafe;
use std::error::Error;

#[doc(hidden)]
pub trait AsDynError<'a>: Sealed {
Expand Down
35 changes: 35 additions & 0 deletions src/display.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use core::fmt::Display;
#[cfg(feature = "std")]
use std::path::{self, Path, PathBuf};

#[doc(hidden)]
Expand All @@ -21,6 +22,7 @@ where
}
}

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

Expand All @@ -30,6 +32,7 @@ impl<'a> AsDisplay<'a> for Path {
}
}

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

Expand All @@ -42,5 +45,37 @@ impl<'a> AsDisplay<'a> for PathBuf {
#[doc(hidden)]
pub trait Sealed {}
impl<T: Display> Sealed for &T {}
#[cfg(feature = "std")]
impl Sealed for Path {}
#[cfg(feature = "std")]
impl Sealed for PathBuf {}

// Add a synthetic second impl of AsDisplay to prevent the "single applicable
// impl" rule from making too weird inference decision based on the single impl
// for &T, which could lead to code that compiles with thiserror's std feature
// off but breaks under feature unification when std is turned on by an
// unrelated crate.
#[cfg(not(feature = "std"))]
mod placeholder {
use super::{AsDisplay, Sealed};
use core::fmt::{self, Display};

pub struct Placeholder;

impl<'a> AsDisplay<'a> for Placeholder {
type Target = Self;

#[inline]
fn as_display(&'a self) -> Self::Target {
Placeholder
}
}

impl Display for Placeholder {
fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
unreachable!()
}
}

impl Sealed for Placeholder {}
}
12 changes: 9 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@
//!
//! [`anyhow`]: https://github.com/dtolnay/anyhow

#![no_std]
#![doc(html_root_url = "https://docs.rs/thiserror/1.0.68")]
#![allow(
clippy::module_name_repetitions,
Expand All @@ -270,6 +271,11 @@
#[cfg(all(thiserror_nightly_testing, not(error_generic_member_access)))]
compile_error!("Build script probe failed to compile.");

#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "std")]
extern crate std as core;

mod aserror;
mod display;
#[cfg(error_generic_member_access)]
Expand All @@ -287,9 +293,9 @@ pub mod __private {
#[cfg(error_generic_member_access)]
#[doc(hidden)]
pub use crate::provide::ThiserrorProvide;
#[cfg(not(thiserror_no_backtrace_type))]
#[doc(hidden)]
pub use std::backtrace::Backtrace;
pub use core::error::Error;
#[cfg(all(feature = "std", not(thiserror_no_backtrace_type)))]
#[doc(hidden)]
pub use std::error::Error;
pub use std::backtrace::Backtrace;
}
2 changes: 1 addition & 1 deletion src/provide.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::error::{Error, Request};
use core::error::{Error, Request};

#[doc(hidden)]
pub trait ThiserrorProvide: Sealed {
Expand Down
16 changes: 16 additions & 0 deletions tests/no-std/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "thiserror_no_std_test"
version = "0.0.0"
authors = ["David Tolnay <dtolnay@gmail.com>"]
edition = "2021"
publish = false

[lib]
path = "test.rs"

[dependencies]
thiserror = { path = "../..", default-features = false }

[features]
default = ["std"]
std = ["thiserror/std"]
58 changes: 58 additions & 0 deletions tests/no-std/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#![no_std]

use thiserror::Error;

#[derive(Error, Debug)]
pub enum Error {
#[error("Error::E")]
E(#[from] SourceError),
}

#[derive(Error, Debug)]
#[error("SourceError {field}")]
pub struct SourceError {
pub field: i32,
}

#[cfg(test)]
mod tests {
use crate::{Error, SourceError};
use core::error::Error as _;
use core::fmt::{self, Write};
use core::mem;

struct Buf<'a>(&'a mut [u8]);

impl Write for Buf<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
if s.len() <= self.0.len() {
let (out, rest) = mem::take(&mut self.0).split_at_mut(s.len());
out.copy_from_slice(s.as_bytes());
self.0 = rest;
Ok(())
} else {
Err(fmt::Error)
}
}
}

#[test]
fn test() {
let source = SourceError { field: -1 };
let error = Error::from(source);

let source = error
.source()
.unwrap()
.downcast_ref::<SourceError>()
.unwrap();

let mut msg = [b'~'; 17];
write!(Buf(&mut msg), "{error}").unwrap();
assert_eq!(msg, *b"Error::E~~~~~~~~~");

let mut msg = [b'~'; 17];
write!(Buf(&mut msg), "{source}").unwrap();
assert_eq!(msg, *b"SourceError -1~~~");
}
}

0 comments on commit 9df02d6

Please sign in to comment.