2017-02-06 9 views
1

В то время как Rust предоставляет str.as_bytes, я хочу скопировать строку в буфер фиксированного размера, где в буфер скопированы только полные значения unicode-scalar и вместо этого усечены с нулевым терминатором, написанным в конце, в C термины, я бы назвал это utf8 осведомленным strlcpy(то есть - он копирует в буфер фиксированного размера и обеспечивает его завершение нулем).Эффективная усеченная строка копирует `str` в` [u8] `(utf8 aware strlcpy)?


Это функция, которую я придумал, но я полагаю, есть лучшие способы сделать это в Русте:

// return the number of bytes written to 
pub fn strlcpy_utf8(utf8_dst: &mut [u8], str_src: &str) -> usize { 
    let utf8_dst_len = utf8_dst.len(); 
    if utf8_dst_len == 0 { 
     return 0; 
    } 
    let mut index: usize = 0; 
    if utf8_dst_len > 1 { 
     let mut utf8_buf: [u8; 4] = [0; 4]; 
     for c in str_src.chars() { 
      let len_utf8 = c.len_utf8(); 
      let index_next = index + len_utf8; 
      c.encode_utf8(&mut utf8_buf); 
      if index_next >= utf8_dst_len { 
       break; 
      } 
      utf8_dst[index..index_next].clone_from_slice(&utf8_buf[0..len_utf8]); 
      index = index_next; 
     } 
    } 
    utf8_dst[index] = 0; 
    return index + 1; 
} 

Примечание): Я понимаю, что это не идеально, так как множество UCS может составлять один глиф, однако результат, по крайней мере, будет способен декодировать обратно в str.

ответ

4

Rust's str имеет удобный способ char_indices, когда вам нужно знать фактические границы символов. Это сразу бы упростить функцию несколько:

pub fn strlcpy_utf8(utf8_dst: &mut [u8], str_src: &str) -> usize { 
    let utf8_dst_len = utf8_dst.len(); 
    if utf8_dst_len == 0 { 
     return 0; 
    } 
    let mut last_index = 0; 
    for (idx, _) in str_src.char_indices() { 
     if (idx+1) > utf8_dst_len { 
      break; 
     } 
     last_index = idx; 
    } 
    utf8_dst[0..last_index].copy_from_slice(&str_src.as_bytes()[0..last_index]); 
    utf8_dst[last_index] = 0; 
    return last_index + 1; 
} 

Playground

Однако вы на самом деле не нужно перебирать каждый символ за исключением при копировании, как выясняется, что это легко найти границу в UTF8; Руст имеет str::is_char_boundary(). Это позволяет вместо смотреть назад с конца:

pub fn strlcpy_utf8(utf8_dst: &mut [u8], str_src: &str) -> usize { 
    let utf8_dst_len = utf8_dst.len(); 
    if utf8_dst_len == 0 { 
     return 0; 
    } 
    let mut last_index = min(utf8_dst_len-1, str_src.len()); 
    while last_index > 0 && !str_src.is_char_boundary(last_index) { 
     last_index -= 1; 
    } 
    utf8_dst[0..last_index].copy_from_slice(&str_src.as_bytes()[0..last_index]); 
    utf8_dst[last_index] = 0; 
    return last_index + 1; 
} 

Playground

+0

Не было бы лучше написать 'let mut last_index = :: std :: cmp :: min (utf8_dst_len - 1, str_src.len());' для второго примера? Таким образом, он не подсчитывает один за другим, когда строка меньше, чем '[u8]'? – ideasman42

+0

Да, спасибо - обновлено. –

+0

Отдайте предпочтение этой версии второго примера: https://bitbucket.org/snippets/ideasman42/GqB8o - возможно, это просто личное предпочтение, но оно позволяет избежать запуска 'is_char_boundary', когда это не нужно, что делает его более явным, когда строка должна быть усечена. – ideasman42

-1

На основании ответа Криса Эмерсона и @ предложение Матье-M, чтобы удалить избыточную проверку.

// returns the number of bytes written to 
pub fn strlcpy_utf8(utf8_dst: &mut [u8], str_src: &str) -> usize { 
    let utf8_dst_len = utf8_dst.len(); 
    if utf8_dst_len == 0 { 
     return 0; 
    } 
    // truncate if 'str_src' is too long 
    let mut last_index = str_src.len(); 
    if last_index >= utf8_dst_len { 
     last_index = utf8_dst_len - 1; 
     // no need to check last_index > 0 here, 
     // is_char_boundary covers that case 
     while !str_src.is_char_boundary(last_index) { 
      last_index -= 1; 
     } 
    } 
    utf8_dst[0..last_index].clone_from_slice(&str_src.as_bytes()[0..last_index]); 
    utf8_dst[last_index] = 0; 
    return last_index + 1; 
} 

@ChrisEmerson: Я отправляю это, так как это код, который я собираюсь использовать для моего проекта, не стесняйтесь, чтобы обновить свой ответ с изменениями, если вам нравится, и я удалю этот ответ.

+0

re: down-vote, что не так с этим примером кода? – ideasman42