如何获得'& str'如果NUL终结符不在切片的末尾,那么从NUL终止的字节片开始?

时间:2017-02-06 11:12:51

标签: string rust

虽然CStr通常用于FFI,但我正在从&[u8]读取NUL终止并确保其为有效的UTF-8,因此不需要进行检查。

然而,NUL终结符不一定在切片的末尾。有什么方法可以将其作为&str

建议使用CStr::from_bytes_with_nul,但这会对内部\0字符造成恐慌(当\0不是最后一个字符时)。

3 个答案:

答案 0 :(得分:4)

我会使用迭代器适配器来查找第一个零字节的索引:

pub unsafe fn str_from_u8_nul_utf8_unchecked(utf8_src: &[u8]) -> &str {
    let nul_range_end = utf8_src.iter()
        .position(|&c| c == b'\0')
        .unwrap_or(utf8_src.len()); // default to length if no `\0` present
    ::std::str::from_utf8_unchecked(&utf8_src[0..nul_range_end])
}

这样做的主要优点是需要捕获所有情况(例如数组中没有0)。

如果您想要检查格式良好的UTF-8的版本:

pub fn str_from_u8_nul_utf8(utf8_src: &[u8]) -> Result<&str, std::str::Utf8Error> {
    let nul_range_end = utf8_src.iter()
        .position(|&c| c == b'\0')
        .unwrap_or(utf8_src.len()); // default to length if no `\0` present
    ::std::str::from_utf8(&utf8_src[0..nul_range_end])
}

答案 1 :(得分:1)

三种可能的其他方式,主要是仅使用std。

中的函数
use std::ffi::CStr;
use std::str;

fn str_from_null_terminated_utf8_safe(s: &[u8]) -> &str {
    if s.iter().any(|&x| x == 0) {
        unsafe { str_from_null_terminated_utf8(s) }
    } else {
        str::from_utf8(s).unwrap()
    }
}

// unsafe: s must contain a null byte
unsafe fn str_from_null_terminated_utf8(s: &[u8]) -> &str {
    CStr::from_ptr(s.as_ptr() as *const _).to_str().unwrap()
}

// unsafe: s must contain a null byte, and be valid utf-8
unsafe fn str_from_null_terminated_utf8_unchecked(s: &[u8]) -> &str {
    str::from_utf8_unchecked(CStr::from_ptr(s.as_ptr() as *const _).to_bytes())
}

略微搁置:此线程中所有选项的基准测试结果:

使用s = b"\0"

test dtwood::bench_str_from_null_terminated_utf8           ... bench:           9 ns/iter (+/- 0)
test dtwood::bench_str_from_null_terminated_utf8_safe      ... bench:          10 ns/iter (+/- 3)
test dtwood::bench_str_from_null_terminated_utf8_unchecked ... bench:           5 ns/iter (+/- 1)
test ideasman42::bench_str_from_u8_nul_utf8_unchecked      ... bench:           1 ns/iter (+/- 0)
test ker::bench_str_from_u8_nul_utf8                       ... bench:           4 ns/iter (+/- 0)
test ker::bench_str_from_u8_nul_utf8_unchecked             ... bench:           1 ns/iter (+/- 0)

s = b"abcdefghij\0klmnop"

test dtwood::bench_str_from_null_terminated_utf8           ... bench:          15 ns/iter (+/- 2)
test dtwood::bench_str_from_null_terminated_utf8_safe      ... bench:          20 ns/iter (+/- 2)
test dtwood::bench_str_from_null_terminated_utf8_unchecked ... bench:           6 ns/iter (+/- 0)
test ideasman42::bench_str_from_u8_nul_utf8_unchecked      ... bench:           7 ns/iter (+/- 0)
test ker::bench_str_from_u8_nul_utf8                       ... bench:          15 ns/iter (+/- 2)
test ker::bench_str_from_u8_nul_utf8_unchecked             ... bench:           5 ns/iter (+/- 0)

s = b"abcdefghij" * 512 + "\0klmnopqrs"

test dtwood::bench_str_from_null_terminated_utf8           ... bench:         351 ns/iter (+/- 35)
test dtwood::bench_str_from_null_terminated_utf8_safe      ... bench:       1,987 ns/iter (+/- 274)
test dtwood::bench_str_from_null_terminated_utf8_unchecked ... bench:         170 ns/iter (+/- 18)
test ideasman42::bench_str_from_u8_nul_utf8_unchecked      ... bench:       2,466 ns/iter (+/- 292)
test ker::bench_str_from_u8_nul_utf8                       ... bench:       1,971 ns/iter (+/- 209)
test ker::bench_str_from_u8_nul_utf8_unchecked             ... bench:       1,828 ns/iter (+/- 205)

因此,如果您非常关注性能,可能最好使用您的特定数据集进行基准测试 - dtwood::str:from_null_terminated_utf8_unchecked似乎使用更长的字符串表现更好,但ker::bench_str_from_u8_nul_utf8_unchecked在较小的字符集上做得更好(&lt; 20)字符串。

答案 2 :(得分:0)

此示例使用简单的for循环查找第一个NUL字节,然后使用Rust的标准库将切片作为&str返回(引用原始数据 - 零拷贝)。

使用闭包找到第一个NUL字节可能有更好的方法:

pub unsafe fn str_from_u8_nul_utf8_unchecked(utf8_src: &[u8]) -> &str {
    // does Rust have a built-in 'memchr' equivalent? 
    let mut nul_range_end = 1_usize;
    for b in utf8_src {
        if *b == 0 {
            break;
        }
        nul_range_end += 1;
    }
    return ::std::str::from_utf8_unchecked(&utf8_src[0..nul_range_end]);
}

虽然utf8_src.iter().position(|&c| c == b'\0').unwrap_or(utf8_src.len());返回第一个NUL字节(或总长度),但Rust 1.15并没有将其优化为类似memchr的内容,因此for循环可能不是那么糟糕现在的选择。