Skip to content

Commit c5bd195

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

File tree

4 files changed

+164
-0
lines changed

4 files changed

+164
-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

+127
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,133 @@ 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
276+
Self::new_uninit(unsafe { mem::transmute(buf) })
277+
}
278+
279+
/// Creates a new cursor wrapping the provided uninitialized buffer.
280+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
281+
pub fn new_uninit(buf: &'a mut [mem::MaybeUninit<u8>]) -> WriteCursor<'a> {
282+
WriteCursor { buf, utf8_len: 0 }
283+
}
284+
285+
/// Returns the total capacity of the cursor's underlying buffer, in bytes.
286+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
287+
pub fn capacity(&self) -> usize {
288+
self.buf.len()
289+
}
290+
291+
/// Returns how many bytes have been written to the cursor's underlying buffer.
292+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
293+
pub fn written(&self) -> usize {
294+
self.utf8_len
295+
}
296+
297+
/// Returns the written portion of the cursor's underlying buffer as a
298+
/// byte slice.
299+
///
300+
/// The returned slice contains valid UTF-8.
301+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
302+
pub fn as_bytes(&self) -> &[u8] {
303+
let utf8 = &self.buf[..self.utf8_len];
304+
// SAFETY: The buffer is incrementally initialized by `write_str`, which
305+
// updates `utf8_len`.
306+
unsafe { mem::MaybeUninit::slice_assume_init_ref(utf8) }
307+
}
308+
309+
/// Consumes the cursor and returns the written portion of the cursor's
310+
/// underlying buffer as a byte slice.
311+
///
312+
/// The returned slice contains valid UTF-8.
313+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
314+
pub fn into_bytes(self) -> &'a [u8] {
315+
let utf8 = &self.buf[..self.utf8_len];
316+
// SAFETY: The buffer is incrementally initialized by `write_str`, which
317+
// updates `utf8_len`.
318+
unsafe { mem::MaybeUninit::slice_assume_init_ref(utf8) }
319+
}
320+
321+
/// Returns the written portion of the cursor's underlying buffer as a `&str`.
322+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
323+
pub fn as_str(&self) -> &str {
324+
// SAFETY: The buffer is only initialized by copying from `str` values.
325+
unsafe { str::from_utf8_unchecked(self.as_bytes()) }
326+
}
327+
328+
/// Consumes the cursor and returns the written portion of the cursor's
329+
/// underlying buffer as a `&str`.
330+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
331+
pub fn into_str(self) -> &'a str {
332+
// SAFETY: The buffer is only initialized by copying from `str` values.
333+
unsafe { str::from_utf8_unchecked(self.into_bytes()) }
334+
}
335+
336+
/// Allows the [`write!`] macro to write to a `WriteCursor`.
337+
///
338+
/// This method should generally not be invoked manually. It exists so that
339+
/// the `write!` macro can write to a `WriteCursor` without needing a
340+
/// `use std::fmt::Write` declaration.
341+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
342+
pub fn write_fmt(&mut self, args: Arguments<'_>) -> Result {
343+
Write::write_fmt(self, args)
344+
}
345+
}
346+
347+
#[unstable(feature = "fmt_write_cursor", issue = "none")]
348+
impl Write for WriteCursor<'_> {
349+
fn write_str(&mut self, s: &str) -> Result {
350+
let b = s.as_bytes();
351+
let avail = &mut self.buf[self.utf8_len..];
352+
if b.len() > avail.len() {
353+
return Err(Error);
354+
}
355+
mem::MaybeUninit::write_slice(&mut avail[..b.len()], b);
356+
self.utf8_len += b.len();
357+
Ok(())
358+
}
359+
}
360+
234361
/// Configuration for formatting.
235362
///
236363
/// 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)