雖然CStr
通常用於FFI,但我正在從&[u8]
讀取,它是NUL終止的,並且確保它是有效的UTF-8,因此不需要進行檢查。如果NUL終止符不在切片的末尾,如何從NUL終止的字節切片中獲取'&str'?
但是,NUL終止符不一定在切片的末尾。有什麼好方法可以將它作爲&str
?
建議使用CStr::from_bytes_with_nul
,但在內部\0
字符(當\0
不是最後一個字符時)發生這種恐慌。
雖然CStr
通常用於FFI,但我正在從&[u8]
讀取,它是NUL終止的,並且確保它是有效的UTF-8,因此不需要進行檢查。如果NUL終止符不在切片的末尾,如何從NUL終止的字節切片中獲取'&str'?
但是,NUL終止符不一定在切片的末尾。有什麼好方法可以將它作爲&str
?
建議使用CStr::from_bytes_with_nul
,但在內部\0
字符(當\0
不是最後一個字符時)發生這種恐慌。
我會用迭代器適配器找到第一個零字節的索引:
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])
}
這不是一個最佳的解決方案,因爲'position'比一個大字符串需要比'memchr'更長的時間。 – BurntSushi5
@ BurntSushi5:似乎並沒有被優化:( –
本示例使用簡單的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
循環可能不是現在這樣一個糟糕的選擇。
三種可能的其他方式。
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個字符)的字符串更好。
我也鼓勵你展示非未經檢查的版本,對於其他找到這個答案卻不能保證字符串的人已經是UTF-8。 – Shepmaster
請注意,您的整體功能也應該是'不安全';它要求調用者確保utf8_src的確在UTF-8中以避免UB。 – whitequark
'let last_index = utf8_src.position(|&b | b == 0).map_or(utf8_src.len(),| i | i - 1)'? – Dogbert
在這種情況下,需要一個nul字節的第一個索引:例如:'memchr(utf8,0,utf8。len())' – ideasman42