Skip to content

Commit cf6e87c

Browse files
committed
feat: Make Response const-constructable
This allows the following code to work: ```rust const BAD_REQUEST_RESPONSE: Response<Empty<Bytes>> = Response::from_parts( { let mut parts = Parts::new(); parts.status = StatusCode::BAD_REQUEST; parts }, Empty::<Bytes>::new(), ); ```
1 parent 51167ce commit cf6e87c

5 files changed

Lines changed: 75 additions & 49 deletions

File tree

src/extensions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ pub struct Extensions {
4141
impl Extensions {
4242
/// Create an empty `Extensions`.
4343
#[inline]
44-
pub fn new() -> Extensions {
44+
pub const fn new() -> Extensions {
4545
Extensions { map: None }
4646
}
4747

src/header/map.rs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ pub use self::into_header_name::IntoHeaderName;
4545
pub struct HeaderMap<T = HeaderValue> {
4646
// Used to mask values to get an index
4747
mask: Size,
48-
indices: Box<[Pos]>,
48+
// Invariant: indices should not be grown, appended, truncated or shrunk.
49+
// It should be a Box<[Pos]> but that is not const constructable.
50+
indices: Vec<Pos>,
4951
entries: Vec<Bucket<T>>,
5052
extra_values: Vec<ExtraValue<T>>,
5153
danger: Danger,
@@ -446,16 +448,25 @@ impl HeaderMap {
446448
/// assert_eq!(0, map.capacity());
447449
/// ```
448450
#[inline]
449-
pub fn new() -> Self {
450-
Self::default()
451+
pub const fn new() -> Self {
452+
// Can't use HeaderMap::default as it's not const
453+
HeaderMap {
454+
mask: 0,
455+
indices: Vec::new(),
456+
entries: Vec::new(),
457+
extra_values: Vec::new(),
458+
danger: Danger::Green,
459+
}
451460
}
452461
}
453462

454463
impl<T> Default for HeaderMap<T> {
455464
fn default() -> Self {
465+
// Can't use HeaderMap::new as it's only implemented for T = HeaderValue,
466+
// widening its implementation would cause inference issues.
456467
HeaderMap {
457468
mask: 0,
458-
indices: Box::new([]), // as a ZST, this doesn't actually allocate anything
469+
indices: Vec::new(),
459470
entries: Vec::new(),
460471
extra_values: Vec::new(),
461472
danger: Danger::Green,
@@ -527,7 +538,7 @@ impl<T> HeaderMap<T> {
527538

528539
Ok(HeaderMap {
529540
mask: (raw_cap - 1) as Size,
530-
indices: vec![Pos::none(); raw_cap].into_boxed_slice(),
541+
indices: vec![Pos::none(); raw_cap],
531542
entries: Vec::with_capacity(usable_capacity(raw_cap)),
532543
extra_values: Vec::new(),
533544
danger: Danger::Green,
@@ -726,7 +737,7 @@ impl<T> HeaderMap<T> {
726737

727738
if self.entries.is_empty() {
728739
self.mask = raw_cap as Size - 1;
729-
self.indices = vec![Pos::none(); raw_cap].into_boxed_slice();
740+
self.indices = vec![Pos::none(); raw_cap];
730741
self.entries = Vec::with_capacity(usable_capacity(raw_cap));
731742
} else {
732743
self.try_grow(raw_cap)?;
@@ -1727,7 +1738,7 @@ impl<T> HeaderMap<T> {
17271738
if len == 0 {
17281739
let new_raw_cap = 8;
17291740
self.mask = 8 - 1;
1730-
self.indices = vec![Pos::none(); new_raw_cap].into_boxed_slice();
1741+
self.indices = vec![Pos::none(); new_raw_cap];
17311742
self.entries = Vec::with_capacity(usable_capacity(new_raw_cap));
17321743
} else {
17331744
let raw_cap = self.indices.len();
@@ -1758,10 +1769,7 @@ impl<T> HeaderMap<T> {
17581769

17591770
// visit the entries in an order where we can simply reinsert them
17601771
// into self.indices without any bucket stealing.
1761-
let old_indices = mem::replace(
1762-
&mut self.indices,
1763-
vec![Pos::none(); new_raw_cap].into_boxed_slice(),
1764-
);
1772+
let old_indices = mem::replace(&mut self.indices, vec![Pos::none(); new_raw_cap]);
17651773
self.mask = new_raw_cap.wrapping_sub(1) as Size;
17661774

17671775
for &pos in &old_indices[first_ideal..] {

src/response.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ pub struct Response<T> {
186186
///
187187
/// The HTTP response head consists of a status, version, and a set of
188188
/// header fields.
189-
#[derive(Clone)]
189+
#[derive(Clone, Default)]
190190
#[non_exhaustive]
191191
pub struct Parts {
192192
/// The response's status
@@ -249,7 +249,7 @@ impl<T> Response<T> {
249249
/// assert_eq!(*response.body(), "hello world");
250250
/// ```
251251
#[inline]
252-
pub fn new(body: T) -> Response<T> {
252+
pub const fn new(body: T) -> Response<T> {
253253
Response {
254254
head: Parts::new(),
255255
body,
@@ -272,7 +272,7 @@ impl<T> Response<T> {
272272
/// assert_eq!(*response.body(), "hello world");
273273
/// ```
274274
#[inline]
275-
pub fn from_parts(parts: Parts, body: T) -> Response<T> {
275+
pub const fn from_parts(parts: Parts, body: T) -> Response<T> {
276276
Response { head: parts, body }
277277
}
278278

@@ -286,7 +286,7 @@ impl<T> Response<T> {
286286
/// assert_eq!(response.status(), StatusCode::OK);
287287
/// ```
288288
#[inline]
289-
pub fn status(&self) -> StatusCode {
289+
pub const fn status(&self) -> StatusCode {
290290
self.head.status
291291
}
292292

@@ -315,7 +315,7 @@ impl<T> Response<T> {
315315
/// assert_eq!(response.version(), Version::HTTP_11);
316316
/// ```
317317
#[inline]
318-
pub fn version(&self) -> Version {
318+
pub const fn version(&self) -> Version {
319319
self.head.version
320320
}
321321

@@ -344,7 +344,7 @@ impl<T> Response<T> {
344344
/// assert!(response.headers().is_empty());
345345
/// ```
346346
#[inline]
347-
pub fn headers(&self) -> &HeaderMap<HeaderValue> {
347+
pub const fn headers(&self) -> &HeaderMap<HeaderValue> {
348348
&self.head.headers
349349
}
350350

@@ -374,7 +374,7 @@ impl<T> Response<T> {
374374
/// assert!(response.extensions().get::<i32>().is_none());
375375
/// ```
376376
#[inline]
377-
pub fn extensions(&self) -> &Extensions {
377+
pub const fn extensions(&self) -> &Extensions {
378378
&self.head.extensions
379379
}
380380

@@ -404,7 +404,7 @@ impl<T> Response<T> {
404404
/// assert!(response.body().is_empty());
405405
/// ```
406406
#[inline]
407-
pub fn body(&self) -> &T {
407+
pub const fn body(&self) -> &T {
408408
&self.body
409409
}
410410

@@ -500,12 +500,12 @@ impl<T: fmt::Debug> fmt::Debug for Response<T> {
500500

501501
impl Parts {
502502
/// Creates a new default instance of `Parts`
503-
fn new() -> Parts {
503+
pub const fn new() -> Parts {
504504
Parts {
505505
status: StatusCode::default(),
506506
version: Version::default(),
507-
headers: HeaderMap::default(),
508-
extensions: Extensions::default(),
507+
headers: HeaderMap::new(),
508+
extensions: Extensions::new(),
509509
}
510510
}
511511
}

src/status.rs

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -70,18 +70,17 @@ impl StatusCode {
7070
/// assert!(err.is_err());
7171
/// ```
7272
#[inline]
73-
pub fn from_u16(src: u16) -> Result<StatusCode, InvalidStatusCode> {
74-
if !(100..1000).contains(&src) {
75-
return Err(InvalidStatusCode::new());
73+
pub const fn from_u16(src: u16) -> Result<StatusCode, InvalidStatusCode> {
74+
if matches!(src, 100..=999) {
75+
if let Some(code) = NonZeroU16::new(src) {
76+
return Ok(StatusCode(code));
77+
}
7678
}
77-
78-
NonZeroU16::new(src)
79-
.map(StatusCode)
80-
.ok_or_else(InvalidStatusCode::new)
79+
Err(InvalidStatusCode::new())
8180
}
8281

8382
/// Converts a `&[u8]` to a status code.
84-
pub fn from_bytes(src: &[u8]) -> Result<StatusCode, InvalidStatusCode> {
83+
pub const fn from_bytes(src: &[u8]) -> Result<StatusCode, InvalidStatusCode> {
8584
if src.len() != 3 {
8685
return Err(InvalidStatusCode::new());
8786
}
@@ -95,9 +94,11 @@ impl StatusCode {
9594
}
9695

9796
let status = (a * 100) + (b * 10) + c;
98-
NonZeroU16::new(status)
99-
.map(StatusCode)
100-
.ok_or_else(InvalidStatusCode::new)
97+
if let Some(code) = NonZeroU16::new(status) {
98+
Ok(StatusCode(code))
99+
} else {
100+
Err(InvalidStatusCode::new())
101+
}
101102
}
102103

103104
/// Returns the `u16` corresponding to this `StatusCode`.
@@ -168,38 +169,47 @@ impl StatusCode {
168169
/// let status = http::StatusCode::OK;
169170
/// assert_eq!(status.canonical_reason(), Some("OK"));
170171
/// ```
171-
pub fn canonical_reason(&self) -> Option<&'static str> {
172+
pub const fn canonical_reason(&self) -> Option<&'static str> {
172173
canonical_reason(self.0.get())
173174
}
174175

175176
/// Check if status is within 100-199.
176177
#[inline]
177-
pub fn is_informational(&self) -> bool {
178-
(100..200).contains(&self.0.get())
178+
pub const fn is_informational(&self) -> bool {
179+
matches!(self.0.get(), 100..=199)
179180
}
180181

181182
/// Check if status is within 200-299.
182183
#[inline]
183-
pub fn is_success(&self) -> bool {
184-
(200..300).contains(&self.0.get())
184+
pub const fn is_success(&self) -> bool {
185+
matches!(self.0.get(), 200..=299)
185186
}
186187

187188
/// Check if status is within 300-399.
188189
#[inline]
189-
pub fn is_redirection(&self) -> bool {
190-
(300..400).contains(&self.0.get())
190+
pub const fn is_redirection(&self) -> bool {
191+
matches!(self.0.get(), 300..=399)
191192
}
192193

193194
/// Check if status is within 400-499.
194195
#[inline]
195-
pub fn is_client_error(&self) -> bool {
196-
(400..500).contains(&self.0.get())
196+
pub const fn is_client_error(&self) -> bool {
197+
matches!(self.0.get(), 400..=499)
197198
}
198199

199200
/// Check if status is within 500-599.
200201
#[inline]
201-
pub fn is_server_error(&self) -> bool {
202-
(500..600).contains(&self.0.get())
202+
pub const fn is_server_error(&self) -> bool {
203+
matches!(self.0.get(), 500..=599)
204+
}
205+
206+
/// Returns the "default value" for a type.
207+
///
208+
/// This is implemented as an associated function in addition
209+
/// to the [`Default`] trait, to allow `const` usage.
210+
#[inline]
211+
pub const fn default() -> StatusCode {
212+
StatusCode::OK
203213
}
204214
}
205215

@@ -231,7 +241,7 @@ impl fmt::Display for StatusCode {
231241
impl Default for StatusCode {
232242
#[inline]
233243
fn default() -> StatusCode {
234-
StatusCode::OK
244+
StatusCode::default()
235245
}
236246
}
237247

@@ -313,7 +323,7 @@ macro_rules! status_codes {
313323

314324
}
315325

316-
fn canonical_reason(num: u16) -> Option<&'static str> {
326+
const fn canonical_reason(num: u16) -> Option<&'static str> {
317327
match num {
318328
$(
319329
$num => Some($phrase),
@@ -523,7 +533,7 @@ status_codes! {
523533
}
524534

525535
impl InvalidStatusCode {
526-
fn new() -> InvalidStatusCode {
536+
const fn new() -> InvalidStatusCode {
527537
InvalidStatusCode { _priv: () }
528538
}
529539
}

src/version.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ impl Version {
4040

4141
/// `HTTP/3.0`
4242
pub const HTTP_3: Version = Version(Http::H3);
43+
44+
/// Returns the "default value" for a type.
45+
///
46+
/// This is implemented as an associated function in addition
47+
/// to the [`Default`] trait, to allow `const` usage.
48+
pub const fn default() -> Version {
49+
Version::HTTP_11
50+
}
4351
}
4452

4553
#[derive(PartialEq, PartialOrd, Copy, Clone, Eq, Ord, Hash)]
@@ -55,7 +63,7 @@ enum Http {
5563
impl Default for Version {
5664
#[inline]
5765
fn default() -> Version {
58-
Version::HTTP_11
66+
Version::default()
5967
}
6068
}
6169

0 commit comments

Comments
 (0)