Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow disabling std dependency on 1.81+ #373

Merged
merged 1 commit into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/no-std/Cargo.toml
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
12 changes: 12 additions & 0 deletions tests/no-std/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "thiserror_no_std_test"
version = "0.0.0"
authors = ["David Tolnay <[email protected]>"]
edition = "2021"
publish = false

[lib]
path = "test.rs"

[dependencies]
thiserror = { path = "../..", default-features = false }
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~~~");
}
}
Loading