是否可以将字节解码为 UTF-8,将错误转换为 Rust 中的转义序列?

Posted

技术标签:

【中文标题】是否可以将字节解码为 UTF-8,将错误转换为 Rust 中的转义序列?【英文标题】:Is it possible to decode bytes to UTF-8, converting errors to escape sequences in Rust? 【发布时间】:2017-05-18 05:39:18 【问题描述】:

在 Rust 中,可以通过执行以下操作从字节中获取 UTF-8:

if let Ok(s) = str::from_utf8(some_u8_slice) 
    println!("example ", s);

这要么有效,要么无效,但 Python 具有处理错误的能力,例如:

s = some_bytes.decode(encoding='utf-8', errors='surrogateescape');

在此示例中,参数surrogateescape 将无效的 utf-8 序列转换为转义码,因此不会忽略或替换无法解码的文本,而是将它们替换为有效的字节文字表达式 @987654325 @。详情请见:Python docs。

Rust 是否有办法从字节中获取 UTF-8 字符串,从而避免错误而不是完全失败?

【问题讨论】:

【参考方案1】:

是的,通过String::from_utf8_lossy

fn main() 
    let text = [104, 101, 0xFF, 108, 111];
    let s = String::from_utf8_lossy(&text);
    println!("", s); // he�lo

如果您需要对流程进行更多控制,可以使用std::str::from_utf8,正如other answer 所建议的那样。但是,没有理由像它所建议的那样双重验证字节。

一个快速破解的例子:

use std::str;

fn example(mut bytes: &[u8]) -> String 
    let mut output = String::new();

    loop 
        match str::from_utf8(bytes) 
            Ok(s) => 
                // The entire rest of the string was valid UTF-8, we are done
                output.push_str(s);
                return output;
            
            Err(e) => 
                let (good, bad) = bytes.split_at(e.valid_up_to());

                if !good.is_empty() 
                    let s = unsafe 
                        // This is safe because we have already validated this
                        // UTF-8 data via the call to `str::from_utf8`; there's
                        // no need to check it a second time
                        str::from_utf8_unchecked(good)
                    ;
                    output.push_str(s);
                

                if bad.is_empty() 
                    //  No more data left
                    return output;
                

                // Do whatever type of recovery you need to here
                output.push_str("<badbyte>");

                // Skip the bad byte and try again
                bytes = &bad[1..];
            
        
    


fn main() 
    let r = example(&[104, 101, 0xFF, 108, 111]);
    println!("", r); // he<badbyte>lo

您可以扩展它以获取替换坏字节的值,处理坏字节的闭包等。例如:

fn example(mut bytes: &[u8], handler: impl Fn(&mut String, &[u8])) -> String 
    // ...    
                handler(&mut output, bad);
    // ...

let r = example(&[104, 101, 0xFF, 108, 111], |output, bytes| 
    use std::fmt::Write;
    write!(output, "\\U", bytes[0]).unwrap()
);
println!("", r); // he\U255lo

另见:

How do I convert a Vector of bytes (u8) to a string How to print a u8 slice as text if I don't care about the particular encoding?。

【讨论】:

请注意,from_utf8_lossy 没有像 Python 那样提供不同的错误处理方式。而不是转义,无效的 utf-8 序列被替换为 U+FFFD(匹配 Python 的 replace 行为)。所以我认为这个问题的简短答案是,尽管它仍然值得一提from_utf8_lossy 对所提出问题的简短回答(“是否可以将字节解码为 UTF-8,在 Rust 中将错误转换为转义序列?”或“Rust 是否有办法获得 UTF -8 来自字节的字符串,它可以处理错误而不会完全失败?") 是 no?我很确定这段代码就是这样做的。 from_utf8_lossy 状态的文档:"在此转换过程中,from_utf8_lossy() 将用 U+FFFD 替换字符替换任何无效的 UTF-8 序列,如下所示:�"。所以这是一个替换,而不是一个转义序列。该答案的第一部分显示了如何使用转义序列进行转换:***.com/a/41450295/432509 @ideasman42 在这种情况下,转义序列是什么意思?有什么例子? 转义序列不是替换字符,而是使用某些标识符 \N... 来显示字符,因此它不是 有损,而是包含字符串中的字符 (通常作为数字)。有关一些示例,请参阅:docs.python.org/3/library/codecs.html#error-handlers。如 OP 中所述,Python 可以为此使用surrogateescape。将澄清这个问题,因为任何不熟悉 Python 的人都不会觉得它很有帮助。【参考方案2】:

您可以:

    使用strict UTF-8 decoding 自己构建它,它会返回一个错误,指示解码失败的位置,然后您可以转义。但这效率低下,因为您将对每次失败的尝试进行两次解码。

    试试3rd party crates,它提供了更多可定制的字符集解码器。

【讨论】:

将每次失败的尝试解码两次——你能在这方面进一步扩展吗?我没有看到双重解码尝试。 回复:“但这样效率低下,因为每次失败的尝试都会解码两次。” 似乎应该有更好的方法可以在一个小函数中完成,类​​似于这个答案,但支持有效的 utf8:***.com/a/41450295/432509 @Shepmaster,在出现错误的情况下,您认为单次通过有可能实现吗? @ideasman42 更好的方法是我建议的第二个选项。 从头开始,解析直到遇到错误,跳过错误/添加所需的任何标记,然后在错误后继续解析。您只读取每个字节一次,对所有数据进行一次传递。这就是为什么我要问我错过了什么。

以上是关于是否可以将字节解码为 UTF-8,将错误转换为 Rust 中的转义序列?的主要内容,如果未能解决你的问题,请参考以下文章

在页面js 中,怎么将中文字符串转换成2个字节长度16进制数;并在js 环境下解码16进

将Base64解码为UTF-8而不是单字节编码文本

Ruby 1.9:将字节数组转换为具有多字节 UTF-8 字符的字符串

如何将 utf-8 字节偏移量转换为 utf-8 字符偏移量

编码解码

创建 smtp() 时出现 Python smtplib 错误:“utf-8”编解码器无法解码字节