2017-02-06 19 views
1

虽然CStr通常用于FFI,但我正在从&[u8]读取,它是NUL终止的,并且确保它是有效的UTF-8,因此不需要进行检查。如果NUL终止符不在切片的末尾,如何从NUL终止的字节切片中获取'&str'?

但是,NUL终止符不一定在切片的末尾。有什么好方法可以将它作为&str

建议使用CStr::from_bytes_with_nul,但在内部\0字符(当\0不是最后一个字符时)发生这种恐慌。

+2

请注意,您的整体功能也应该是'不安全';它要求调用者确保utf8_src的确在UTF-8中以避免UB。 – whitequark

+0

'let last_index = utf8_src.position(|&b | b == 0).map_or(utf8_src.len(),| i | i - 1)'? – Dogbert

+0

在这种情况下,需要一个nul字节的第一个索引:例如:'memchr(utf8,0,utf8。len())' – ideasman42

回答

2

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

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]) 
} 
+0

这不是一个最佳的解决方案,因为'position'比一个大字符串需要比'memchr'更长的时间。 – BurntSushi5

+0

@ BurntSushi5:似乎并没有被优化:( –

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]); 
} 

虽然第一NUL字节utf8_src.iter().position(|&c| c == b'\0').unwrap_or(utf8_src.len());返回(或总长度),锈病1.15不优化它到memchr之类的东西,所以for循环可能不是现在这样一个糟糕的选择。

1

三种可能的其他方式。

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不小(< 20个字符)的字符串更好。

+0

我也鼓励你展示非未经检查的版本,对于其他找到这个答案却不能保证字符串的人已经是UTF-8。 – Shepmaster