String::from_utf8_lossy¶
Level: reference · for working programmers
One line: Decodes bytes, replacing every invalid sequence with U+FFFD (�) — returns a Cow, so valid input costs nothing.
Stable since 1.0.0.
It cannot fail, which is exactly when to use it and when not to. Display, logging, and best-effort recovery: yes. Anything where the bytes have to survive a round trip: no — the substitution is lossy in the name and in fact, and the original bytes are gone.
The return is Cow<'_, str>: Borrowed when the input was already valid (no allocation at all), Owned only when something had to be replaced. Checking which one you got is a cheap way to know whether the input was clean.
One replacement character can stand for several bad bytes — the Unicode recommendation is one per maximal invalid subsequence — so the count of � is not the count of bad bytes.
Example¶
string_from_utf8_lossy.rs in full — pasted here by tools/run_examples.py from the file CI compiles and runs.
use std::borrow::Cow;
fn main() {
println!("{:?}", String::from_utf8_lossy(&[104, 105]));
println!("{:?}", String::from_utf8_lossy(&[104, 0xff, 105]));
// Borrowed when clean, Owned only when something was replaced.
for bytes in [&[104u8, 105][..], &[104, 0xff, 105][..]] {
match String::from_utf8_lossy(bytes) {
Cow::Borrowed(s) => println!("borrowed {s:?} -- input was valid"),
Cow::Owned(s) => println!("owned {s:?} -- something was replaced"),
}
}
// Lossy: the bytes do not survive a round trip.
let original = vec![104, 0xff, 105];
let decoded = String::from_utf8_lossy(&original);
println!("{:?} -> {:?}", original, decoded.as_bytes());
// One replacement can stand for SEVERAL bad bytes: 0xE2 0x82 is the
// truncated start of a 3-byte sequence, so it is one bad run, not two.
for bytes in [&[0xffu8, 0xfe, 0xfd][..], &[0xE2, 0x82][..]] {
let out = String::from_utf8_lossy(bytes);
println!("{} bad bytes -> {} replacement char(s) {:?}",
bytes.len(),
out.chars().filter(|c| *c == '\u{FFFD}').count(),
out);
}
}
Verified output of string_from_utf8_lossy.rs — regenerated by tools/run_examples.py, never hand-typed.
"hi"
"h�i"
borrowed "hi" -- input was valid
owned "h�i" -- something was replaced
[104, 255, 105] -> [104, 239, 191, 189, 105]
3 bad bytes -> 3 replacement char(s) "���"
2 bad bytes -> 1 replacement char(s) "�"
See also¶
String::from_utf8— failing instead of substitutingString::from_utf16_lossy— the same idea for UTF-16str::from_utf8— the borrowing, checked version
String::from_utf8_lossy in the standard library ↗
Po polsku¶
Ta funkcja nie może się nie udać — i to jest zarazem powód, żeby jej używać, i powód, żeby jej nie używać. Do wyświetlania, logowania i ratowania tego, co się da: jak najbardziej. Wszędzie tam, gdzie bajty mają przetrwać podróż w obie strony: nie, bo podstawienie jest stratne nie tylko z nazwy — [104, 255, 105] wychodzi jako [104, 239, 191, 189, 105] i bajtu 0xFF już nie odzyskasz.
Wynikiem jest Cow<'_, str> i to nie jest wyłącznie szczegół implementacyjny: Borrowed znaczy „wejście było poprawne, nic nie zaalokowano”, Owned — „coś podmieniono”. Sprawdzenie wariantu jest więc darmowym testem czystości danych, tańszym niż drugie przejście przez str::from_utf8. Uważaj natomiast na liczenie znaków �: Unicode zaleca jeden znak zastępczy na maksymalny niepoprawny podciąg, więc trzy przypadkowe bajty FF FE FD dają trzy znaki, ale E2 82 — ucięty początek sekwencji trzybajtowej — daje jeden. Liczba � nie jest liczbą zepsutych bajtów.
Szukaj po polsku: stratne dekodowanie · Cow w Ruscie · znak zastępczy U+FFFD · rust from_utf8_lossy Cow · rust maximal subpart replacement character