Skip to content

Commit 12ebd34

Browse files
committed
add FromUtf8Error in sync with std
The FromUtf8Error allows you to regain ownership over the byte vector that you have moved into the from_utf8 method. This mirrors std.
1 parent 78eab14 commit 12ebd34

2 files changed

Lines changed: 80 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [Unreleased]
99
- Added `from_bytes_truncating_at_nul` to `CString`
10+
- Added `FromUtf8Error` to `heapless::string`
11+
- [breaking-change] Changed error type of `String::from_utf8` from `core::str::Utf8Error` to `heapless::string::FromUtf8Error`
1012

1113
## [v0.9.2] 2025-11-12
1214

src/string/mod.rs

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use core::{
44
borrow,
55
char::DecodeUtf16Error,
66
cmp::Ordering,
7-
fmt,
8-
fmt::{Arguments, Write},
7+
error::Error,
8+
fmt::{self, Arguments, Display, Write},
99
hash, iter,
1010
ops::{self, Range, RangeBounds},
1111
str::{self, Utf8Error},
@@ -23,6 +23,63 @@ use crate::{
2323
mod drain;
2424
pub use drain::Drain;
2525

26+
/// Error type of the [`String::from_utf8`] function.
27+
///
28+
/// [`Self::into_bytes`] will give back the byte vector which was used in the conversion attempt.
29+
///
30+
/// Equivalent of `std::string::FromUtf8Error`.
31+
///
32+
/// # Examples
33+
///
34+
/// ```
35+
/// use heapless::{String, Vec, string::FromUtf8Error};
36+
///
37+
/// let data = [0, 159, 146, 150];
38+
/// let mut vec = Vec::<u8, 4>::new();
39+
/// vec.extend_from_slice(&data);
40+
///
41+
/// let e: FromUtf8Error<4> = String::from_utf8(vec).unwrap_err();
42+
///
43+
/// assert_eq!(e.utf8_error().valid_up_to(), 1);
44+
/// assert_eq!(e.as_bytes(), &data);
45+
/// assert_eq!(e.into_bytes(), data);
46+
/// ```
47+
#[derive(Debug, Clone, PartialEq, Eq)]
48+
pub struct FromUtf8Error<const N: usize, LenT: LenType = usize> {
49+
bytes: Vec<u8, N, LenT>,
50+
error: Utf8Error,
51+
}
52+
53+
impl<const N: usize, LenT: LenType> FromUtf8Error<N, LenT> {
54+
/// Returns a slice of [`u8`]s bytes that were attempted to convert to a [`String`].
55+
pub fn as_bytes(&self) -> &[u8] {
56+
&self.bytes
57+
}
58+
59+
/// Returns the bytes that were attempted to convert to a [`String`].
60+
pub fn into_bytes(self) -> Vec<u8, N, LenT> {
61+
self.bytes
62+
}
63+
64+
/// Fetch the [`Utf8Error`] to get more details about the conversion failure.
65+
pub fn utf8_error(&self) -> Utf8Error {
66+
self.error
67+
}
68+
}
69+
70+
impl<const N: usize, LenT: LenType> Display for FromUtf8Error<N, LenT> {
71+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72+
core::fmt::Display::fmt(&self, f)
73+
}
74+
}
75+
76+
impl<const N: usize, LenT: LenType> Error for FromUtf8Error<N, LenT> {
77+
/// returns the underlying [`Utf8Error`]
78+
fn source(&self) -> Option<&(dyn Error + 'static)> {
79+
Some(&self.error)
80+
}
81+
}
82+
2683
/// A possible error value when converting a [`String`] from a UTF-16 byte slice.
2784
///
2885
/// This type is the error type for the [`from_utf16`] method on [`String`].
@@ -219,6 +276,13 @@ impl<LenT: LenType, const N: usize> String<N, LenT> {
219276

220277
/// Convert UTF-8 bytes into a `String`.
221278
///
279+
/// # Errors
280+
///
281+
/// Returns [`Err`] if the bytes are not valid UTF-8. The error includes
282+
/// a description as to why the bytes are not UTF-8.
283+
/// The error also contains the moved [`Vec`], such that
284+
/// you can regain ownership of it.
285+
///
222286
/// # Examples
223287
///
224288
/// Basic usage:
@@ -231,25 +295,28 @@ impl<LenT: LenType, const N: usize> String<N, LenT> {
231295
///
232296
/// let sparkle_heart: String<4> = String::from_utf8(sparkle_heart)?;
233297
/// assert_eq!("💖", sparkle_heart);
234-
/// # Ok::<(), core::str::Utf8Error>(())
298+
/// # Ok::<(), heapless::string::FromUtf8Error<4>>(())
235299
/// ```
236300
///
237301
/// Invalid UTF-8:
238302
///
239303
/// ```
240-
/// use core::str::Utf8Error;
241-
/// use heapless::{String, Vec};
304+
/// use heapless::{String, Vec, string::FromUtf8Error};
242305
///
306+
/// let data = [0, 159, 146, 150];
243307
/// let mut vec = Vec::<u8, 4>::new();
244-
/// vec.extend_from_slice(&[0, 159, 146, 150]);
308+
/// vec.extend_from_slice(&data);
245309
///
246-
/// let e: Utf8Error = String::from_utf8(vec).unwrap_err();
247-
/// assert_eq!(e.valid_up_to(), 1);
248-
/// # Ok::<(), core::str::Utf8Error>(())
310+
/// let e: FromUtf8Error<4> = String::from_utf8(vec).unwrap_err();
311+
/// assert_eq!(e.utf8_error().valid_up_to(), 1);
312+
/// assert_eq!(e.into_bytes(), data);
313+
/// # Ok::<(), heapless::string::FromUtf8Error<4>>(())
249314
/// ```
250315
#[inline]
251-
pub fn from_utf8(vec: Vec<u8, N, LenT>) -> Result<Self, Utf8Error> {
252-
core::str::from_utf8(&vec)?;
316+
pub fn from_utf8(vec: Vec<u8, N, LenT>) -> Result<Self, FromUtf8Error<N, LenT>> {
317+
if let Err(error) = core::str::from_utf8(&vec) {
318+
return Err(FromUtf8Error { bytes: vec, error });
319+
}
253320

254321
// SAFETY: UTF-8 invariant has just been checked by `str::from_utf8`.
255322
Ok(unsafe { Self::from_utf8_unchecked(vec) })

0 commit comments

Comments
 (0)