Const & 'static CStr - PullRequest
       22

Const & 'static CStr

0 голосов
/ 12 октября 2019

Я не нашел ничего в стандартной библиотеке о том, как сделать const &'static CStr. Я пытался создать свой собственный макрос для преобразования &'static str литерала в &'static CStr:

macro_rules! cstr {
    ($e: expr) => {{
        const buffer: &str = concat!($e, "\0");
        unsafe {std::ffi::CStr::from_bytes_with_nul_unchecked(buffer.as_bytes())}
    }}                                                                           
}     

Но у него есть пара проблем:

  1. Это прекрасно работаетв типичных случаях, но если expr содержит нулевой байт, он вызывает неопределенное поведение
  2. str::as_bytes не const, поэтому &CStr не является константным

Есть ли способ создать const &'static CStr?

Ответы [ 2 ]

2 голосов
/ 12 октября 2019

A CStr является заимствованным типом и, как таковое, не создается "самостоятельно". Под капотом это не намного больше, чем ссылка на CString, и может быть создано из:

  • Заимствование CString (очевидно). Оригинал (источник) CString нельзя отбрасывать, а срок действия CStr действителен только до тех пор, пока существует источник
  • Из фрагмента байтов через CStr::from_bytes_with_nul. CStr действителен только до тех пор, пока исходный срез (который сам по себе действителен только до тех пор, пока исходные данные выделены где-то )

Создание CStrчерез CString просто:

let cstring:CString = CString::new("foobar".as_bytes()).unwrap();
let cstr:&CStr = cstring.as_c_str();
println!("{:?}", cstr);

Преобразование существующего среза также просто:

let cstr2:&CStr = CStr::from_bytes_with_nul("foobar\0".as_bytes()).unwrap();
println!("{:?}", cstr2);

Обратите внимание, что время их жизни, очевидно, опять же будет зависеть от времени жизнивсего, что вы использовали для создания &CStr - как указано в параметре времени жизни в его объявлении


Сохранение для потомков : 'static не было жестким требованием

Чтобы создать const &'static CStr, вы будете бороться, и вам понадобится внешний ящик для конкретного макроса (lazy_static), но это выполнимо, например:

#[macro_use] extern crate lazy_static;
use std::ffi::CStr;

lazy_static! {
    static ref FOO:&'static CStr = unsafe {
        CStr::from_bytes_with_nul_unchecked("foobar\0".as_bytes())
    };
}

fn test(input: &'static CStr) {
    println!("{:?}", FOO.to_str());
}

fn main() {
    test(&FOO);
}

Смысл lazy_static в том, чтобы разрешать вызовы функций при определении статических ссылок;мы можем использовать это для построения нашего CStr на лету, и, поскольку это статическая ссылка, заимствование действительно до 'static включительно. Миссия выполнена.

0 голосов
/ 12 октября 2019

Для этого есть ящик, byte_strings . Чтобы подвести итог ящика, основная идея состоит в том, чтобы использовать объединение с &'static [u8] (или &'static str) членом и &'static CStr членом:

union transmute {
    src: &'static [u8],
    dst: &'static ::std::ffi::CStr,
}

Так как создание союзов const и доступполе const union также равно const, чтение dst фактически является const mem :: transmute. Поскольку CStr в настоящее время является просто оболочкой для [c_char], значение &[u8] можно безопасно перевести в &CStr, однако в будущем представление CStr, скорее всего, изменится. Вы можете проверить, что &CStr имеет тот же размер, что и &[u8], используя небольшой хак с длинами массивов нулевого размера:

const transmute_is_sound_guard: [(); std::mem::size_of::<&'static [u8]>()]
    = [(); std::mem::size_of::<&'static ::std::ffi::CStr>()];

Если они не имеют одинаковый размер, Тип проверки Руст будет жаловаться. Собрав все это вместе, вы можете создать макрос, чтобы сделать const &'static CStr:

use std::ffi::CStr;
use std::mem::size_of;

macro_rules! unsafe_cstr {
    ($e: expr) => {{
        union Transmute {
            src: &'static str,
            dst: &'static CStr,
        }

        const _TRANSMUTE_CHECK: [(); size_of::<&'static str>()]
            = [(); size_of::<&'static CStr>()];

        const RES: &'static CStr = unsafe {
            (Transmute { src: concat!($e, "\0") }).dst
        };

        RES
    }}                                                                           
}

fn main() {
    const C: &'static CStr = unsafe_cstr!("Hello, World!");
    println!("{:?}", C)
}

К сожалению, этот макрос по-прежнему небезопасен, поскольку он не проверяет нулевое значениебайтов в срезе &str, что может быть сделано только с процедурным макросом. Ящик byte_strings содержит такой макрос , а также макросы для конкатенации байтовых строковых литералов и других вспомогательных макросов.

...