Skip to content

Commit 6f487eb

Browse files
author
Flix
committed
Avoid unnecessary allocations
1 parent c6ab5fd commit 6f487eb

4 files changed

Lines changed: 56 additions & 50 deletions

File tree

src/error.rs

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
//! Error type implementation.
22
3-
use ::alloc::{string::ToString, vec};
3+
use ::alloc::{borrow::Cow, boxed::Box, vec, vec::Vec};
44
use ::core::{
55
any::Any,
66
error::Error,
77
fmt::{Debug, Display, Formatter, Result as FmtResult},
88
panic::Location,
99
};
1010

11-
use crate::features::{AnyDebugSendSync, Box, ErrorSendSync, String, Vec};
11+
use crate::features::{AnyDebugSendSync, ErrorSendSync};
1212

1313
/// Error information for humans.
1414
/// Error message with location information.
1515
#[derive(Debug)]
1616
pub(crate) struct HumanInfo {
1717
/// Message text.
18-
pub(crate) message: String,
18+
pub(crate) message: Cow<'static, str>,
1919
/// Location of occurrence.
2020
pub(crate) location: &'static Location<'static>,
2121
}
@@ -37,9 +37,9 @@ pub(crate) enum Info {
3737
/// Contextual information for machines.
3838
Machine(MachineInfo),
3939
}
40-
// Ensure size of Context is as expected. Can be adjusted though.
40+
// Ensure niche-optimization is active.
4141
const _: () = {
42-
assert!(size_of::<Info>() == 32);
42+
assert!(size_of::<Info>() == size_of::<HumanInfo>());
4343
};
4444

4545
/// Generic rich error type for use within `Result`s, for libraries and applications.
@@ -138,27 +138,26 @@ impl CtxError {
138138
#[track_caller]
139139
#[must_use]
140140
#[inline]
141-
pub fn new<T: ToString>(message: T) -> Self {
142-
let infos = vec![Info::Human(HumanInfo {
143-
message: message.to_string(),
144-
location: Location::caller(),
145-
})];
141+
pub fn new<C>(context: C) -> Self
142+
where
143+
C: Into<Cow<'static, str>>,
144+
{
145+
let infos =
146+
vec![Info::Human(HumanInfo { message: context.into(), location: Location::caller() })];
146147
Self(CtxErrorImpl { infos, ..Default::default() })
147148
}
148149

149150
/// Create new error from source error.
150151
#[track_caller]
151152
#[must_use]
152153
#[inline]
153-
pub fn new_with_source<T, E>(message: T, source: E) -> Self
154+
pub fn new_with_source<C, E>(context: C, source: E) -> Self
154155
where
155-
T: ToString,
156+
C: Into<Cow<'static, str>>,
156157
E: ErrorSendSync + 'static,
157158
{
158-
let infos = vec![Info::Human(HumanInfo {
159-
message: message.to_string(),
160-
location: Location::caller(),
161-
})];
159+
let infos =
160+
vec![Info::Human(HumanInfo { message: context.into(), location: Location::caller() })];
162161
Self(CtxErrorImpl { infos, source: Some(Box::new(source)) })
163162
}
164163

@@ -178,7 +177,7 @@ impl CtxError {
178177
#[inline]
179178
pub fn context<C>(self, context: C) -> Self
180179
where
181-
C: ToString,
180+
C: Into<Cow<'static, str>>,
182181
{
183182
Self(self.0.context(context))
184183
}
@@ -265,9 +264,9 @@ impl CtxErrorImpl {
265264
#[inline]
266265
pub fn context<C>(mut self, context: C) -> Self
267266
where
268-
C: ToString,
267+
C: Into<Cow<'static, str>>,
269268
{
270-
let context = HumanInfo { message: context.to_string(), location: Location::caller() };
269+
let context = HumanInfo { message: context.into(), location: Location::caller() };
271270
self.infos.push(Info::Human(context));
272271
self
273272
}
@@ -292,20 +291,34 @@ impl CtxErrorImpl {
292291
/// This will override existing attachments of the same type. If you want to add attachments of
293292
/// the same type, use `attach` instead.
294293
#[must_use]
295-
pub fn attach_override<C>(mut self, context: C) -> Self
294+
pub fn attach_override<C>(mut self, mut context: C) -> Self
296295
where
297296
C: AnyDebugSendSync + 'static,
298297
{
299-
let context = MachineInfo { attachment: Box::new(context) };
300-
self.infos.retain(|info| match info {
301-
Info::Human(_) => true,
298+
let mut inserted = false;
299+
#[expect(trivial_casts, reason = "Not that trivial as it seems? False positive")]
300+
self.infos.retain_mut(|info| match info {
302301
Info::Machine(ctx) => {
303-
#[expect(trivial_casts, reason = "Not that trivial as it seems? False positive")]
304-
let any = (ctx.attachment.as_ref()) as &(dyn Any + 'static);
305-
!any.is::<C>()
302+
if let Some(content) =
303+
(ctx.attachment.as_mut() as &mut (dyn Any + 'static)).downcast_mut::<C>()
304+
{
305+
if !inserted {
306+
core::mem::swap(content, &mut context);
307+
inserted = true;
308+
true // First attachment of same type, was replaced with new value, so keep it.
309+
} else {
310+
false // Another attachment of the same type, remove duplicate.
311+
}
312+
} else {
313+
true // Attachment of different type.
314+
}
306315
}
316+
_ => true,
307317
});
308-
self.infos.push(Info::Machine(context));
318+
if !inserted {
319+
// No existing attachment of the same type was found to be replaced, so add a new one.
320+
self.infos.push(Info::Machine(MachineInfo { attachment: Box::new(context) }));
321+
}
309322
self
310323
}
311324

src/features.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,3 @@ impl<T: Any + Debug + SendSync> AnyDebugSendSync for T {}
4242
/// Error trait with send/sync.
4343
pub trait ErrorSendSync: Error + SendSync {}
4444
impl<T: Error + SendSync> ErrorSendSync for T {}
45-
46-
47-
/// Box type.
48-
pub type Box<T> = alloc::boxed::Box<T>;
49-
/// Vec type for stacks.
50-
pub type Vec<T> = alloc::vec::Vec<T>;
51-
/// String type.
52-
pub type String = alloc::string::String;

src/results.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Helpers on `Result` types for conversion and context addition.
22
3-
use ::alloc::string::ToString;
3+
use ::alloc::borrow::Cow;
44

55
use crate::{
66
CtxError,
@@ -14,15 +14,15 @@ pub trait CtxResultExt: Sized {
1414
#[must_use]
1515
fn context<C>(self, context: C) -> Self
1616
where
17-
C: ToString;
17+
C: Into<Cow<'static, str>>;
1818

1919
/// Add human context to the error via a closure.
2020
#[track_caller]
2121
#[must_use]
2222
fn context_with<F, C>(self, context_fn: F) -> Self
2323
where
2424
F: FnOnce() -> C,
25-
C: ToString;
25+
C: Into<Cow<'static, str>>;
2626

2727
/// Add machine context to the error.
2828
///
@@ -68,7 +68,7 @@ impl<T> CtxResultExt for Result<T, CtxError> {
6868
#[inline]
6969
fn context<C>(self, context: C) -> Self
7070
where
71-
C: ToString,
71+
C: Into<Cow<'static, str>>,
7272
{
7373
// Cannot use `map_err` because closures cannot have `#[track_caller]` yet.
7474
match self {
@@ -82,7 +82,7 @@ impl<T> CtxResultExt for Result<T, CtxError> {
8282
fn context_with<F, C>(self, context_fn: F) -> Self
8383
where
8484
F: FnOnce() -> C,
85-
C: ToString,
85+
C: Into<Cow<'static, str>>,
8686
{
8787
// Cannot use `map_err` because closures cannot have `#[track_caller]` yet.
8888
match self {
@@ -133,14 +133,14 @@ pub trait ConvertResult<T, E>: Sized {
133133
#[track_caller]
134134
fn context<C>(self, context: C) -> Result<T, CtxError>
135135
where
136-
C: ToString;
136+
C: Into<Cow<'static, str>>;
137137

138138
/// Add human context to the error via a closure.
139139
#[track_caller]
140140
fn context_with<F, C>(self, context_fn: F) -> Result<T, CtxError>
141141
where
142142
F: FnOnce(&E) -> C,
143-
C: ToString;
143+
C: Into<Cow<'static, str>>;
144144

145145
/// Add machine context to the error.
146146
///
@@ -185,7 +185,7 @@ where
185185
#[inline]
186186
fn context<C>(self, context: C) -> Result<T, CtxError>
187187
where
188-
C: ToString,
188+
C: Into<Cow<'static, str>>,
189189
{
190190
// Cannot use `map_err` because closures cannot have `#[track_caller]` yet.
191191
match self {
@@ -199,7 +199,7 @@ where
199199
fn context_with<F, C>(self, context_fn: F) -> Result<T, CtxError>
200200
where
201201
F: FnOnce(&E) -> C,
202-
C: ToString,
202+
C: Into<Cow<'static, str>>,
203203
{
204204
// Cannot use `map_err` because closures cannot have `#[track_caller]` yet.
205205
match self {
@@ -259,14 +259,14 @@ pub trait ConvertOption<T>: Sized {
259259
#[track_caller]
260260
fn context<C>(self, context: C) -> Result<T, CtxError>
261261
where
262-
C: ToString;
262+
C: Into<Cow<'static, str>>;
263263

264264
/// Convert `None` to an error and add human context to the error via a closure.
265265
#[track_caller]
266266
fn context_with<F, C>(self, context_fn: F) -> Result<T, CtxError>
267267
where
268268
F: FnOnce() -> C,
269-
C: ToString;
269+
C: Into<Cow<'static, str>>;
270270

271271
/// Convert `None` to an error and add machine context to the error.
272272
///
@@ -308,7 +308,7 @@ impl<T> ConvertOption<T> for Option<T> {
308308
#[inline]
309309
fn context<C>(self, context: C) -> Result<T, CtxError>
310310
where
311-
C: ToString,
311+
C: Into<Cow<'static, str>>,
312312
{
313313
// Cannot use `ok_or_else` because closures cannot have `#[track_caller]` yet.
314314
match self {
@@ -322,7 +322,7 @@ impl<T> ConvertOption<T> for Option<T> {
322322
fn context_with<F, C>(self, context_fn: F) -> Result<T, CtxError>
323323
where
324324
F: FnOnce() -> C,
325-
C: ToString,
325+
C: Into<Cow<'static, str>>,
326326
{
327327
// Cannot use `ok_or_else` because closures cannot have `#[track_caller]` yet.
328328
match self {

src/tests.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Crate tests.
22
3-
use ::alloc::{format, vec::Vec};
3+
use ::alloc::{borrow::ToOwned, format, vec::Vec};
44
use ::core::{
55
error::Error,
66
fmt::{Display, Formatter, Result as FmtResult},
@@ -95,9 +95,10 @@ fn error_wrapper() {
9595
assert!(error.source().is_some());
9696
}
9797

98+
/// Make sure all the usual types work as context messages.
9899
#[test]
99100
fn context() {
100-
let error = CtxError::new("0").context("1").context("2");
101+
let error = CtxError::new("0").context("1".to_owned()).context("2");
101102
let mut numbers = error.contexts().map(|ctx| ctx.message.parse::<u8>().unwrap());
102103
assert_eq!(numbers.next(), Some(2));
103104
assert_eq!(numbers.next(), Some(1));

0 commit comments

Comments
 (0)