Skip to content

Commit 13970ff

Browse files
authored
feat(profiling): FallibleStringWriter (#1403)
feat(profiling): FallibleStringWriter Co-authored-by: levi.morrison <[email protected]>
1 parent 5036cae commit 13970ff

File tree

2 files changed

+189
-0
lines changed

2 files changed

+189
-0
lines changed
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use core::fmt::{self, Write};
5+
use std::collections::TryReserveError;
6+
7+
/// A `fmt::Write` adapter that grows a `String` using `try_reserve` before
8+
/// each write, returning `fmt::Error` on allocation failure.
9+
#[derive(Debug)]
10+
pub struct FallibleStringWriter {
11+
buf: String,
12+
}
13+
14+
impl Default for FallibleStringWriter {
15+
fn default() -> FallibleStringWriter {
16+
FallibleStringWriter::new()
17+
}
18+
}
19+
20+
impl FallibleStringWriter {
21+
/// Creates a new empty string writer.
22+
pub const fn new() -> Self {
23+
Self { buf: String::new() }
24+
}
25+
26+
/// Creates a new fallible string writer with a previously existing string
27+
/// as the start of the buffer. New writes will append to the end of this.
28+
pub const fn new_from_existing(buf: String) -> FallibleStringWriter {
29+
FallibleStringWriter { buf }
30+
}
31+
32+
/// Tries to reserve capacity for at least additional bytes more than the
33+
/// current length. The allocator may reserve more space to speculatively
34+
/// avoid frequent allocations.
35+
pub fn try_reserve(&mut self, len: usize) -> Result<(), TryReserveError> {
36+
self.buf.try_reserve(len)
37+
}
38+
39+
/// Tries to reserve the minimum capacity for at least `additional` bytes
40+
/// more than the current length. Unlike [`try_reserve`], this will not
41+
/// deliberately over-allocate to speculatively avoid frequent allocations.
42+
///
43+
/// Note that the allocator may give the collection more space than it
44+
/// requests. Therefore, capacity can not be relied upon to be precisely
45+
/// minimal. Prefer [`try_reserve`] if future insertions are expected.
46+
pub fn try_reserve_exact(&mut self, len: usize) -> Result<(), TryReserveError> {
47+
self.buf.try_reserve_exact(len)
48+
}
49+
50+
pub fn try_push_str(&mut self, str: &str) -> Result<(), TryReserveError> {
51+
self.try_reserve(str.len())?;
52+
self.buf.push_str(str);
53+
Ok(())
54+
}
55+
}
56+
57+
impl From<FallibleStringWriter> for String {
58+
fn from(w: FallibleStringWriter) -> String {
59+
w.buf
60+
}
61+
}
62+
63+
impl From<String> for FallibleStringWriter {
64+
fn from(buf: String) -> FallibleStringWriter {
65+
FallibleStringWriter { buf }
66+
}
67+
}
68+
69+
impl Write for FallibleStringWriter {
70+
fn write_str(&mut self, s: &str) -> fmt::Result {
71+
self.try_push_str(s).map_err(|_| fmt::Error)
72+
}
73+
}
74+
75+
#[cfg(test)]
76+
mod tests {
77+
use super::*;
78+
use std::fmt::Write;
79+
80+
#[test]
81+
fn test_new_and_default() {
82+
let writer = FallibleStringWriter::new();
83+
let s: String = writer.into();
84+
assert_eq!(s, "");
85+
86+
let writer = FallibleStringWriter::default();
87+
let s: String = writer.into();
88+
assert_eq!(s, "");
89+
}
90+
91+
#[test]
92+
fn test_write_str() {
93+
let mut writer = FallibleStringWriter::new();
94+
writer.write_str("Hello").unwrap();
95+
writer.write_str(", ").unwrap();
96+
writer.write_str("World!").unwrap();
97+
98+
let s: String = writer.into();
99+
assert_eq!(s, "Hello, World!");
100+
}
101+
102+
#[test]
103+
fn test_write_formatted() {
104+
let mut writer = FallibleStringWriter::new();
105+
write!(writer, "x = {}, ", 10).unwrap();
106+
write!(writer, "y = {}, ", 20).unwrap();
107+
write!(writer, "sum = {}", 10 + 20).unwrap();
108+
109+
let s: String = writer.into();
110+
assert_eq!(s, "x = 10, y = 20, sum = 30");
111+
}
112+
113+
#[test]
114+
fn test_try_push_str() {
115+
let mut writer = FallibleStringWriter::new();
116+
writer.try_push_str("Hello").unwrap();
117+
writer.try_push_str(" ").unwrap();
118+
writer.try_push_str("World").unwrap();
119+
120+
let s: String = writer.into();
121+
assert_eq!(s, "Hello World");
122+
}
123+
124+
#[test]
125+
fn test_try_reserve() {
126+
// Marcus Aurelius, Meditations (public domain)
127+
let strings = [
128+
"The happiness of your life depends upon the quality of your thoughts: ",
129+
"therefore, guard accordingly, and take care that you entertain ",
130+
"no notions unsuitable to virtue and reasonable nature.",
131+
];
132+
let total_len: usize = strings.iter().map(|s| s.len()).sum();
133+
134+
let mut writer = FallibleStringWriter::new();
135+
// Asking for more than is needed just to ensure that the test isn't
136+
// accidentally correct.
137+
let capacity = 2 * total_len + 7;
138+
writer.try_reserve_exact(capacity).unwrap();
139+
140+
// After reserving, we should be able to write all strings (and more).
141+
for s in &strings {
142+
writer.write_str(s).unwrap();
143+
}
144+
145+
let result: String = writer.into();
146+
assert_eq!(result, strings.join(""));
147+
148+
// It can't be less, but an allocator is free to round, even on a
149+
// try_reserve_exact.
150+
assert!(result.capacity() >= capacity);
151+
}
152+
153+
#[test]
154+
fn test_from_existing_string() {
155+
// Test From<String>, new_from_existing, and appending
156+
let s = String::from("start: ");
157+
let mut writer = FallibleStringWriter::from(s);
158+
write!(writer, "{}", 123).unwrap();
159+
assert_eq!(String::from(writer), "start: 123");
160+
161+
// Test new_from_existing
162+
let mut writer = FallibleStringWriter::new_from_existing(String::from("prefix-"));
163+
writer.try_push_str("suffix").unwrap();
164+
assert_eq!(String::from(writer), "prefix-suffix");
165+
}
166+
167+
#[test]
168+
fn test_write_unicode() {
169+
let mut writer = FallibleStringWriter::new();
170+
write!(writer, "Hello 👋 World 🌍").unwrap();
171+
172+
let s: String = writer.into();
173+
assert_eq!(s, "Hello 👋 World 🌍");
174+
}
175+
176+
#[test]
177+
fn test_write_long_string() {
178+
let mut writer = FallibleStringWriter::new();
179+
let long_str = "a".repeat(1000);
180+
181+
writer.write_str(&long_str).unwrap();
182+
183+
let s: String = writer.into();
184+
assert_eq!(s.len(), 1000);
185+
assert_eq!(s, long_str);
186+
}
187+
}

libdd-profiling/src/profiles/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,7 @@
44
pub mod collections;
55
mod compressor;
66
pub mod datatypes;
7+
mod fallible_string_writer;
78

89
pub use compressor::*;
10+
pub use fallible_string_writer::*;

0 commit comments

Comments
 (0)