Skip to content

Commit e4782ae

Browse files
committed
Add core::fmt::WriteCursor for formatting into a borrowed buffer.
1 parent 698fcc8 commit e4782ae

File tree

4 files changed

+165
-0
lines changed

4 files changed

+165
-0
lines changed

library/alloc/src/fmt.rs

+2
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,8 @@ pub use core::fmt::Alignment;
557557
pub use core::fmt::Error;
558558
#[unstable(feature = "debug_closure_helpers", issue = "117729")]
559559
pub use core::fmt::FormatterFn;
560+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
561+
pub use core::fmt::WriteCursor;
560562
#[stable(feature = "rust1", since = "1.0.0")]
561563
pub use core::fmt::{write, Arguments};
562564
#[stable(feature = "rust1", since = "1.0.0")]

library/core/src/fmt/mod.rs

+128
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,134 @@ impl<W: Write + ?Sized> Write for &mut W {
231231
}
232232
}
233233

234+
/// A `WriteCursor` implements [`fmt::Write`] by writing to an in-memory buffer.
235+
///
236+
/// [`fmt::Write`]: self::Write
237+
///
238+
/// # Examples
239+
///
240+
/// ```
241+
/// #![feature(fmt_write_cursor)]
242+
/// use std::fmt::WriteCursor;
243+
///
244+
/// # fn test_write_cursor() -> std::fmt::Result {
245+
/// let mut buf = [0u8; 5];
246+
/// let mut cursor = WriteCursor::new(&mut buf);
247+
/// write!(cursor, "{}", 12345)?;
248+
/// assert_eq!(cursor.as_str(), "12345");
249+
/// assert_eq!(&buf, b"12345");
250+
/// # Ok(())
251+
/// # }
252+
/// ```
253+
#[doc(alias = "sprintf")]
254+
#[doc(alias = "snprintf")]
255+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
256+
pub struct WriteCursor<'a> {
257+
buf: &'a mut [mem::MaybeUninit<u8>],
258+
utf8_len: usize,
259+
}
260+
261+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
262+
impl Debug for WriteCursor<'_> {
263+
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
264+
f.debug_struct("WriteCursor")
265+
.field("capacity", &self.capacity())
266+
.field("written", &self.written())
267+
.finish()
268+
}
269+
}
270+
271+
impl<'a> WriteCursor<'a> {
272+
/// Creates a new cursor wrapping the provided buffer.
273+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
274+
pub fn new(buf: &'a mut [u8]) -> WriteCursor<'a> {
275+
// SAFETY: [T] and [MaybeUninit<T>] have the same layout, and this
276+
// code never writes uninitialized data to the buffer.
277+
Self::new_uninit(unsafe { mem::transmute(buf) })
278+
}
279+
280+
/// Creates a new cursor wrapping the provided uninitialized buffer.
281+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
282+
pub fn new_uninit(buf: &'a mut [mem::MaybeUninit<u8>]) -> WriteCursor<'a> {
283+
WriteCursor { buf, utf8_len: 0 }
284+
}
285+
286+
/// Returns the total capacity of the cursor's underlying buffer, in bytes.
287+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
288+
pub fn capacity(&self) -> usize {
289+
self.buf.len()
290+
}
291+
292+
/// Returns how many bytes have been written to the cursor's underlying buffer.
293+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
294+
pub fn written(&self) -> usize {
295+
self.utf8_len
296+
}
297+
298+
/// Returns the written portion of the cursor's underlying buffer as a
299+
/// byte slice.
300+
///
301+
/// The returned slice contains valid UTF-8.
302+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
303+
pub fn as_bytes(&self) -> &[u8] {
304+
let utf8 = &self.buf[..self.utf8_len];
305+
// SAFETY: The buffer is incrementally initialized by `write_str`, which
306+
// updates `utf8_len`.
307+
unsafe { mem::MaybeUninit::slice_assume_init_ref(utf8) }
308+
}
309+
310+
/// Consumes the cursor and returns the written portion of the cursor's
311+
/// underlying buffer as a byte slice.
312+
///
313+
/// The returned slice contains valid UTF-8.
314+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
315+
pub fn into_bytes(self) -> &'a mut [u8] {
316+
let utf8 = &mut self.buf[..self.utf8_len];
317+
// SAFETY: The buffer is incrementally initialized by `write_str`, which
318+
// updates `utf8_len`.
319+
unsafe { mem::MaybeUninit::slice_assume_init_mut(utf8) }
320+
}
321+
322+
/// Returns the written portion of the cursor's underlying buffer as a `&str`.
323+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
324+
pub fn as_str(&self) -> &str {
325+
// SAFETY: The buffer is only initialized by copying from `str` values.
326+
unsafe { str::from_utf8_unchecked(self.as_bytes()) }
327+
}
328+
329+
/// Consumes the cursor and returns the written portion of the cursor's
330+
/// underlying buffer as a `&str`.
331+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
332+
pub fn into_str(self) -> &'a mut str {
333+
// SAFETY: The buffer is only initialized by copying from `str` values.
334+
unsafe { str::from_utf8_unchecked_mut(self.into_bytes()) }
335+
}
336+
337+
/// Allows the [`write!`] macro to write to a `WriteCursor`.
338+
///
339+
/// This method should generally not be invoked manually. It exists so that
340+
/// the `write!` macro can write to a `WriteCursor` without needing a
341+
/// `use std::fmt::Write` declaration.
342+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
343+
pub fn write_fmt(&mut self, args: Arguments<'_>) -> Result {
344+
Write::write_fmt(self, args)
345+
}
346+
}
347+
348+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
349+
impl Write for WriteCursor<'_> {
350+
fn write_str(&mut self, s: &str) -> Result {
351+
let b = s.as_bytes();
352+
let avail = &mut self.buf[self.utf8_len..];
353+
if b.len() > avail.len() {
354+
return Err(Error);
355+
}
356+
mem::MaybeUninit::write_slice(&mut avail[..b.len()], b);
357+
self.utf8_len += b.len();
358+
Ok(())
359+
}
360+
}
361+
234362
/// Configuration for formatting.
235363
///
236364
/// A `Formatter` represents various options related to formatting. Users do not

library/core/tests/fmt/mod.rs

+34
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,37 @@ fn pad_integral_resets() {
4343

4444
assert_eq!(format!("{Bar:<03}"), "1 0051 ");
4545
}
46+
47+
#[test]
48+
fn write_cursor() {
49+
use core::fmt::WriteCursor;
50+
51+
let mut buf = [0u8; 5];
52+
53+
{
54+
let mut c = WriteCursor::new(&mut buf);
55+
assert!(write!(c, "{}", 12345).is_ok());
56+
assert_eq!(c.as_bytes(), b"12345");
57+
assert_eq!(c.as_str(), "12345");
58+
}
59+
60+
// Writes either fully succeed or leave the cursor unchanged. A cursor that
61+
// failed to write remains valid for smaller writes.
62+
{
63+
let mut c = WriteCursor::new(&mut buf);
64+
assert!(write!(c, "{}", 123456).is_err());
65+
assert_eq!(c.written(), 0);
66+
assert_eq!(c.as_str(), "");
67+
68+
assert!(write!(c, "{}", 1234).is_ok());
69+
assert_eq!(c.written(), 4);
70+
assert_eq!(c.as_str(), "1234");
71+
72+
assert!(write!(c, "{}", 1).is_ok());
73+
assert_eq!(c.written(), 5);
74+
assert_eq!(c.as_str(), "12341");
75+
76+
assert!(write!(c, "{}", 1).is_err());
77+
assert_eq!(c.written(), 5);
78+
}
79+
}

library/core/tests/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#![feature(extern_types)]
3535
#![feature(flt2dec)]
3636
#![feature(fmt_internals)]
37+
#![feature(fmt_write_cursor)]
3738
#![feature(float_minimum_maximum)]
3839
#![feature(future_join)]
3940
#![feature(generic_assert_internals)]

0 commit comments

Comments
 (0)