Skip to content

String::from_utf8_lossy

String methods · Strings

Level: reference · for working programmers

One line: Decodes bytes, replacing every invalid sequence with U+FFFD () — returns a Cow, so valid input costs nothing.

pub fn from_utf8_lossy(v: &[u8]) -> Cow<'_, str>

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_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