From ef5d9d6114c81c40eab8b40176651aecf2bef313 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 2 Jan 2025 07:53:34 -0800 Subject: [PATCH] no_std support. This PR is similar to other no-std PRs, however it takes the approach of using the new [`core::error`] module in Rust 1.81. This means that no-std mode has an MSRV of Rust 1.81, while the existing MSRV of 1.49 is still supported for existing users, as suggested [here]. This PR also preserves semver compatibility, and avoids adding any new dependencies or required features for existing users. And it avoids modifying the tests and benchmark sources, as those don't need to be no-std. And it avoids making any unrelated changes. And, it adds CI coverage and README.md documentation. [here]: https://github.com/hyperium/http/pull/563#issuecomment-2257330014 [`core::error`]: https://doc.rust-lang.org/stable/core/error/index.html Fixes #551. --- .github/workflows/ci.yml | 13 +++++++++++++ Cargo.toml | 11 +++++++---- README.md | 5 +++++ src/byte_str.rs | 3 ++- src/convert.rs | 4 ++-- src/error.rs | 11 +++++++---- src/extensions.rs | 10 +++++++--- src/header/map.rs | 27 ++++++++++++++++++--------- src/header/name.rs | 19 ++++++++++++------- src/header/value.rs | 17 +++++++++++------ src/lib.rs | 5 ++--- src/method.rs | 14 ++++++++++---- src/request.rs | 6 +++--- src/response.rs | 6 +++--- src/status.rs | 12 ++++++++---- src/uri/authority.rs | 10 ++++++---- src/uri/builder.rs | 2 +- src/uri/mod.rs | 14 ++++++++++---- src/uri/path.rs | 8 +++++--- src/uri/port.rs | 2 +- src/uri/scheme.rs | 9 +++++---- src/version.rs | 2 +- 22 files changed, 139 insertions(+), 71 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 365a11e2..6b4f37d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,6 +90,19 @@ jobs: - name: Test run: cargo check -p http + no-std: + name: Check no_std + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Check + run: cargo check --no-default-features --features=no-std + wasm: name: WASM #needs: [style] diff --git a/Cargo.toml b/Cargo.toml index 1a6e9c9d..b8ae6305 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,13 +33,16 @@ exclude = [ ] [features] -default = ["std"] -std = [] +default = ["std", "bytes/default", "fnv/default"] +std = ["bytes/std", "fnv/std"] +no-std = ["hashbrown", "ahash"] [dependencies] -bytes = "1" -fnv = "1.0.5" +bytes = { version = "1", default-features = false } +fnv = { version = "1.0.5", default-features = false } itoa = "1" +hashbrown = { version = "0.15.2", optional = true } +ahash = { version = "0.8.6", default-features = false, optional = true } [dev-dependencies] quickcheck = "1" diff --git a/README.md b/README.md index a0090032..4f1e0c4f 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,11 @@ This project follows the [Tokio MSRV][msrv] and is currently set to `1.49`. [msrv]: https://github.com/tokio-rs/tokio/#supported-rust-versions +# no-std support + +For no-std support, disable the default "std" feature and enable the "no-std" +feature. no-std support has an MSRV of Rust 1.81. + # License Licensed under either of diff --git a/src/byte_str.rs b/src/byte_str.rs index 90872ecb..28bf679b 100644 --- a/src/byte_str.rs +++ b/src/byte_str.rs @@ -1,6 +1,7 @@ use bytes::Bytes; -use std::{ops, str}; +use alloc::string::String; +use core::{ops, str}; #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub(crate) struct ByteStr { diff --git a/src/convert.rs b/src/convert.rs index 682e0ed5..633c0568 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -1,11 +1,11 @@ macro_rules! if_downcast_into { ($in_ty:ty, $out_ty:ty, $val:ident, $body:expr) => {{ - if std::any::TypeId::of::<$in_ty>() == std::any::TypeId::of::<$out_ty>() { + if core::any::TypeId::of::<$in_ty>() == core::any::TypeId::of::<$out_ty>() { // Store the value in an `Option` so we can `take` // it after casting to `&mut dyn Any`. let mut slot = Some($val); // Re-write the `$val` ident with the downcasted value. - let $val = (&mut slot as &mut dyn std::any::Any) + let $val = (&mut slot as &mut dyn core::any::Any) .downcast_mut::>() .unwrap() .take() diff --git a/src/error.rs b/src/error.rs index 762ee1c2..225b3317 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,9 @@ +#[cfg(not(feature = "std"))] +use core::error; +use core::fmt; +use core::result; +#[cfg(feature = "std")] use std::error; -use std::fmt; -use std::result; use crate::header; use crate::header::MaxSizeReached; @@ -132,8 +135,8 @@ impl From for Error { } } -impl From for Error { - fn from(err: std::convert::Infallible) -> Error { +impl From for Error { + fn from(err: core::convert::Infallible) -> Error { match err {} } } diff --git a/src/extensions.rs b/src/extensions.rs index f16d762e..7b007ece 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -1,7 +1,11 @@ -use std::any::{Any, TypeId}; +use alloc::boxed::Box; +use core::any::{Any, TypeId}; +use core::fmt; +use core::hash::{BuildHasherDefault, Hasher}; +#[cfg(not(feature = "std"))] +use hashbrown::HashMap; +#[cfg(feature = "std")] use std::collections::HashMap; -use std::fmt; -use std::hash::{BuildHasherDefault, Hasher}; type AnyMap = HashMap, BuildHasherDefault>; diff --git a/src/header/map.rs b/src/header/map.rs index ebbc5937..40adf206 100644 --- a/src/header/map.rs +++ b/src/header/map.rs @@ -1,10 +1,15 @@ -use std::collections::hash_map::RandomState; -use std::collections::HashMap; -use std::convert::TryFrom; -use std::hash::{BuildHasher, Hash, Hasher}; -use std::iter::{FromIterator, FusedIterator}; -use std::marker::PhantomData; -use std::{fmt, mem, ops, ptr, vec}; +use alloc::boxed::Box; +use alloc::vec; +use alloc::vec::Vec; +use core::convert::TryFrom; +use core::hash::{BuildHasher, Hash, Hasher}; +use core::iter::{FromIterator, FusedIterator}; +use core::marker::PhantomData; +use core::{fmt, mem, ops, ptr}; +#[cfg(feature = "std")] +use std::collections::{hash_map::RandomState, HashMap}; +#[cfg(not(feature = "std"))] +use {ahash::RandomState, hashbrown::HashMap}; use crate::Error; @@ -116,7 +121,7 @@ pub struct IntoIter { /// associated value. #[derive(Debug)] pub struct Keys<'a, T> { - inner: ::std::slice::Iter<'a, Bucket>, + inner: ::core::slice::Iter<'a, Bucket>, } /// `HeaderMap` value iterator. @@ -209,7 +214,7 @@ pub struct ValueIterMut<'a, T> { #[derive(Debug)] pub struct ValueDrain<'a, T> { first: Option, - next: Option<::std::vec::IntoIter>, + next: Option<::alloc::vec::IntoIter>, lt: PhantomData<&'a mut HeaderMap>, } @@ -3574,7 +3579,10 @@ impl fmt::Display for MaxSizeReached { } } +#[cfg(feature = "std")] impl std::error::Error for MaxSizeReached {} +#[cfg(not(feature = "std"))] +impl core::error::Error for MaxSizeReached {} // ===== impl Utils ===== @@ -3736,6 +3744,7 @@ mod into_header_name { mod as_header_name { use super::{Entry, HdrName, HeaderMap, HeaderName, InvalidHeaderName, MaxSizeReached}; + use alloc::string::String; /// A marker trait used to identify values that can be used as search keys /// to a `HeaderMap`. diff --git a/src/header/name.rs b/src/header/name.rs index 3d563f4e..013a586f 100644 --- a/src/header/name.rs +++ b/src/header/name.rs @@ -1,13 +1,18 @@ use crate::byte_str::ByteStr; use bytes::{Bytes, BytesMut}; -use std::borrow::Borrow; -use std::convert::TryFrom; +use alloc::string::String; +use alloc::vec::Vec; +use core::borrow::Borrow; +use core::convert::TryFrom; +#[cfg(not(feature = "std"))] +use core::error::Error; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::mem::MaybeUninit; +use core::str::FromStr; +#[cfg(feature = "std")] use std::error::Error; -use std::fmt; -use std::hash::{Hash, Hasher}; -use std::mem::MaybeUninit; -use std::str::FromStr; /// Represents an HTTP header field name /// @@ -89,7 +94,7 @@ macro_rules! standard_headers { match *self { // Safety: test_parse_standard_headers ensures these &[u8]s are &str-safe. $( - StandardHeader::$konst => unsafe { std::str::from_utf8_unchecked( $name_bytes ) }, + StandardHeader::$konst => unsafe { core::str::from_utf8_unchecked( $name_bytes ) }, )+ } } diff --git a/src/header/value.rs b/src/header/value.rs index 99d1e155..ba6e8416 100644 --- a/src/header/value.rs +++ b/src/header/value.rs @@ -1,11 +1,16 @@ use bytes::{Bytes, BytesMut}; -use std::convert::TryFrom; +use alloc::string::String; +use alloc::vec::Vec; +use core::convert::TryFrom; +#[cfg(not(feature = "std"))] +use core::error::Error; +use core::fmt::Write; +use core::hash::{Hash, Hasher}; +use core::str::FromStr; +use core::{cmp, fmt, str}; +#[cfg(feature = "std")] use std::error::Error; -use std::fmt::Write; -use std::hash::{Hash, Hasher}; -use std::str::FromStr; -use std::{cmp, fmt, str}; use crate::header::name::HeaderName; @@ -234,7 +239,7 @@ impl HeaderValue { } fn from_shared(src: Bytes) -> Result { - HeaderValue::try_from_generic(src, std::convert::identity) + HeaderValue::try_from_generic(src, core::convert::identity) } fn try_from_generic, F: FnOnce(T) -> Bytes>( diff --git a/src/lib.rs b/src/lib.rs index 0ab5bdfd..eac94bbb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -154,10 +154,9 @@ //! ``` #![deny(warnings, missing_docs, missing_debug_implementations)] +#![cfg_attr(not(feature = "std"), no_std)] -//#![cfg_attr(not(feature = "std"), no_std)] -#[cfg(not(feature = "std"))] -compile_error!("`std` feature currently required, support for `no_std` may be added later"); +extern crate alloc; #[cfg(test)] #[macro_use] diff --git a/src/method.rs b/src/method.rs index 7b4584ab..d367c710 100644 --- a/src/method.rs +++ b/src/method.rs @@ -18,10 +18,13 @@ use self::extension::{AllocatedExtension, InlineExtension}; use self::Inner::*; -use std::convert::TryFrom; +use core::convert::TryFrom; +#[cfg(not(feature = "std"))] +use core::error::Error; +use core::str::FromStr; +use core::{fmt, str}; +#[cfg(feature = "std")] use std::error::Error; -use std::str::FromStr; -use std::{fmt, str}; /// The Request Method (VERB) /// @@ -306,7 +309,10 @@ impl Error for InvalidMethod {} mod extension { use super::InvalidMethod; - use std::str; + use alloc::boxed::Box; + use alloc::vec; + use alloc::vec::Vec; + use core::str; #[derive(Clone, PartialEq, Eq, Hash)] // Invariant: the first self.1 bytes of self.0 are valid UTF-8. diff --git a/src/request.rs b/src/request.rs index 324b676c..be58fde9 100644 --- a/src/request.rs +++ b/src/request.rs @@ -52,9 +52,9 @@ //! } //! ``` -use std::any::Any; -use std::convert::TryInto; -use std::fmt; +use core::any::Any; +use core::convert::TryInto; +use core::fmt; use crate::header::{HeaderMap, HeaderName, HeaderValue}; use crate::method::Method; diff --git a/src/response.rs b/src/response.rs index ab9e49bc..17f5d7f1 100644 --- a/src/response.rs +++ b/src/response.rs @@ -61,9 +61,9 @@ //! // ... //! ``` -use std::any::Any; -use std::convert::TryInto; -use std::fmt; +use core::any::Any; +use core::convert::TryInto; +use core::fmt; use crate::header::{HeaderMap, HeaderName, HeaderValue}; use crate::status::StatusCode; diff --git a/src/status.rs b/src/status.rs index 9ad04d20..6c472fb4 100644 --- a/src/status.rs +++ b/src/status.rs @@ -14,11 +14,15 @@ //! assert!(StatusCode::OK.is_success()); //! ``` -use std::convert::TryFrom; +use crate::alloc::borrow::ToOwned; +use core::convert::TryFrom; +#[cfg(not(feature = "std"))] +use core::error::Error; +use core::fmt; +use core::num::NonZeroU16; +use core::str::FromStr; +#[cfg(feature = "std")] use std::error::Error; -use std::fmt; -use std::num::NonZeroU16; -use std::str::FromStr; /// An HTTP status code (`status-code` in RFC 9110 et al.). /// diff --git a/src/uri/authority.rs b/src/uri/authority.rs index 07aa6795..fc7b9bcf 100644 --- a/src/uri/authority.rs +++ b/src/uri/authority.rs @@ -1,7 +1,9 @@ -use std::convert::TryFrom; -use std::hash::{Hash, Hasher}; -use std::str::FromStr; -use std::{cmp, fmt, str}; +use alloc::string::String; +use alloc::vec::Vec; +use core::convert::TryFrom; +use core::hash::{Hash, Hasher}; +use core::str::FromStr; +use core::{cmp, fmt, str}; use bytes::Bytes; diff --git a/src/uri/builder.rs b/src/uri/builder.rs index d5f7f49b..d63cff73 100644 --- a/src/uri/builder.rs +++ b/src/uri/builder.rs @@ -1,4 +1,4 @@ -use std::convert::TryInto; +use core::convert::TryInto; use super::{Authority, Parts, PathAndQuery, Scheme}; use crate::Uri; diff --git a/src/uri/mod.rs b/src/uri/mod.rs index 767f0743..8b2ab57b 100644 --- a/src/uri/mod.rs +++ b/src/uri/mod.rs @@ -23,14 +23,20 @@ //! ``` use crate::byte_str::ByteStr; -use std::convert::TryFrom; use bytes::Bytes; +use alloc::boxed::Box; +use alloc::string::String; +use alloc::vec::Vec; +use core::convert::TryFrom; +#[cfg(not(feature = "std"))] +use core::error::Error; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::str::{self, FromStr}; +#[cfg(feature = "std")] use std::error::Error; -use std::fmt; -use std::hash::{Hash, Hasher}; -use std::str::{self, FromStr}; use self::scheme::Scheme2; diff --git a/src/uri/path.rs b/src/uri/path.rs index df00c415..c8aa0ee3 100644 --- a/src/uri/path.rs +++ b/src/uri/path.rs @@ -1,6 +1,8 @@ -use std::convert::TryFrom; -use std::str::FromStr; -use std::{cmp, fmt, hash, str}; +use alloc::string::String; +use alloc::vec::Vec; +use core::convert::TryFrom; +use core::str::FromStr; +use core::{cmp, fmt, hash, str}; use bytes::Bytes; diff --git a/src/uri/port.rs b/src/uri/port.rs index 2a7028e2..db23ab81 100644 --- a/src/uri/port.rs +++ b/src/uri/port.rs @@ -1,4 +1,4 @@ -use std::fmt; +use core::fmt; use super::{ErrorKind, InvalidUri}; diff --git a/src/uri/scheme.rs b/src/uri/scheme.rs index dbcc8c3f..584794de 100644 --- a/src/uri/scheme.rs +++ b/src/uri/scheme.rs @@ -1,7 +1,8 @@ -use std::convert::TryFrom; -use std::fmt; -use std::hash::{Hash, Hasher}; -use std::str::FromStr; +use alloc::boxed::Box; +use core::convert::TryFrom; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::str::FromStr; use bytes::Bytes; diff --git a/src/version.rs b/src/version.rs index d8b71306..436ca7df 100644 --- a/src/version.rs +++ b/src/version.rs @@ -19,7 +19,7 @@ //! println!("{:?}", http2); //! ``` -use std::fmt; +use core::fmt; /// Represents a version of the HTTP spec. #[derive(PartialEq, PartialOrd, Copy, Clone, Eq, Ord, Hash)]