Каков самый прямой способ конвертировать Path в * c_char?

Учитывая std::path::Path, какой самый прямой способ преобразовать это в std::os::raw::c_char с нулевым завершением? (для перехода к C-функциям, которые идут по пути).

use std::ffi::CString;
use std::os::raw::c_char;
use std::os::raw::c_void;

extern "C" {
    some_c_function(path: *const c_char);
}

fn example_c_wrapper(path: std::path::Path) {
    let path_str_c = CString::new(path.as_os_str().to_str().unwrap()).unwrap();

    some_c_function(path_str_c.as_ptr());
}

Есть ли способ избежать так много промежуточных шагов?

Path -> OsStr -> &str -> CString -> as_ptr()

Ответ 1

Это не так просто, как кажется. Есть одна информация, которую вы не предоставили: в какой кодировке функция C ожидает путь?

В Linux пути - это "просто" массивы байтов (0 - недопустимый), и приложения обычно не пытаются их декодировать. (Однако им, возможно, придется декодировать их с помощью определенной кодировки, например, чтобы отобразить их пользователю, и в этом случае они обычно будут пытаться декодировать их в соответствии с текущей локалью, которая часто будет использовать кодировку UTF-8.)

В Windows это сложнее, потому что есть варианты функций API, которые используют кодовую страницу "ANSI", и варианты, которые используют "Unicode" (UTF-16). Кроме того, Windows не поддерживает установку UTF-8 в качестве кодовой страницы "ANSI". Это означает, что если библиотека специально не ожидает UTF-8 и не преобразует путь в собственное кодирование, передача его в кодированном UTF-8 пути определенно неверна (хотя может показаться, что она работает для строк, содержащих только символы ASCII).

(Я не знаю о других платформах, но это уже достаточно грязно.)

В Rust Path - это просто оболочка для OsStr. OsStr использует платформо-зависимое представление, которое оказывается совместимым с UTF-8, когда строка действительно является допустимой UTF-8, но строки не-UTF-8 используют неопределенную кодировку (в Windows это фактически использует WTF-8, но это не является договорным, в Linux это просто массив байтов как есть).

Прежде чем передать путь к функции C, вы должны определить, в какой кодировке она ожидает строку, и, если она не соответствует кодировке Rust, вам придется преобразовать ее перед упаковкой в CString. Rust не позволяет вам преобразовывать Path или OsStr во что-либо кроме str независимо от платформы. Для целей на основе Unix OsStrExt особенность OsStrExt которая обеспечивает доступ к OsStr в виде фрагмента байтов.

Ржавчина используется для обеспечения to_cstring метод на OsStr, но это никогда не стабилизируется, и она была устаревшей в Rust 1.6.0, как выяснилось, что поведение было неуместно для Windows ( в это возвратило UTF-8 закодирован путь, но для Windows API, не поддерживаю это!).

Ответ 2

Поскольку Path - это всего лишь тонкая оболочка для OsStr, вы можете почти передать ее как есть вашей функции C. Но чтобы быть допустимой строкой C, мы должны добавить завершающий байт NUL. Таким образом, мы должны выделить CString.

С другой стороны, преобразование в str сопряжено с риском (что, если Path не является допустимой строкой UTF-8?), А также излишними затратами: я использую as_bytes() вместо to_str().

fn example_c_wrapper<P: AsRef<std::path::Path>>(path: P) {
    let path_str_c = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap();

    some_c_function(path_str_c.as_ptr());
}

Это для Unix. Я не знаю, как это работает для Windows.

Ответ 3

Если вы пытаетесь создать Vec<u8>, я обычно звоню по телефону и делаю:

#[cfg(unix)]
fn path_to_bytes<P: AsRef<Path>>(path: P) -> Vec<u8> {
    use std::os::unix::ffi::OsStrExt;
    path.as_ref().as_os_str().as_bytes().to_vec()
}

#[cfg(not(unix))]
fn path_to_bytes<P: AsRef<Path>>(path: P) -> Vec<u8> {
    // On Windows, could use std::os::windows::ffi::OsStrExt to encode_wide(),
    // but you end up with a Vec<u16> instead of a Vec<u8>, so that does not
    // really help.
    path.as_ref().to_string_lossy().to_string().into_bytes()
}

Хорошо зная, что пути не-UTF8 в не-UNIX не будут правильно поддерживаться. Обратите внимание, что вам может понадобиться Vec<u8> при работе с Thrift/протокольными буферами, а не с C API.