Есть ли способ обеспечить, чтобы необработанный указатель Rust не использовался после возврата из указанного c стекового фрейма? - PullRequest
2 голосов
/ 08 апреля 2020

Я пишу оболочку Rust для (в основном C стиля) C ++ плагина SDK. Хост подключаемого модуля - это графическое настольное приложение, которое запускает событие l oop. Плагин регулярно вызывается как часть этого события l oop. Всякий раз, когда это происходит, плагин имеет контроль и может вызывать произвольные функции хоста.

Одна C функция, которую я хочу обернуть, возвращает необработанный указатель. Сразу после того, как эта функция вернется, указатель гарантированно будет верной C строкой, поэтому можно безопасно разыменовать ее. Однако после возврата обратного вызова плагина (таким образом возвращая управление хосту) указатель может устареть. Как мне написать для этого оболочку функции ergonomi c, которая в какой-то момент не приведет к неопределенному поведению, например, когда потребитель пытается получить доступ к строке в следующем цикле l oop?

I Думал о следующих подходах:

1. Вернуть принадлежащую строку

Я мог бы немедленно разыменовать указатель и скопировать содержимое в принадлежащее CString:

pub fn get_string_from_host() -> CString {
    let ptr: *const c_char = unsafe { ffi.get_string() };
    unsafe { CStr::from_ptr(ptr).to_owned() }
}

Это самонадеянно - возможно, потребитель моей оболочки не заинтересован в получить принадлежащую строку, потому что они просто хотят провести сравнение (я бы даже сказал, это основной вариант использования). Тогда копирование строки будет пустой тратой.

2. Вернуть необработанный указатель

pub fn get_string_from_host() -> *const c_char {
    unsafe { ffi.get_string() }
}

Это просто переносит проблему на потребителя.

3. Вернуть ссылку CStr (небезопасный метод)

pub unsafe fn get_string_from_host<'a>() -> &'a CStr {
    let ptr: *const c_char = ffi.get_string();
    CStr::from_ptr(ptr)
}

Это небезопасно, поскольку время жизни ссылки не является точным. Доступ к ссылке в более поздний момент времени может привести к неопределенному поведению. Еще один способ перенести проблему на потребителя.

4. Возьмите закрытие вместо того, чтобы вернуть что-то

pub fn with_string_from_host<T>(f: impl Fn(&CStr) -> T) -> T {
    let ptr: *const c_char = unsafe { ffi.get_string() };
    f(unsafe { CStr::from_ptr(ptr) })
}

pub fn consuming_function() {
    let length = with_string_from_host(|s| s.to_bytes().len());
}

Это работает, но к этому действительно нужно привыкнуть.


Ни одно из этих решений действительно не удовлетворяет.

Есть ли способ убедиться, что возвращаемое значение используется «немедленно», что означает, что оно нигде не хранится или никогда не выходит за пределы области действия вызывающего? любой аннотации на весь срок службы, что означает что-то вроде «действует только в текущем стековом кадре» Если бы было, я бы использовал это (только для иллюстрации):

pub fn get_string_from_host() -> &'??? CStr {
    let ptr: *const c_char = unsafe { ffi.get_string() };
    unsafe { CStr::from_ptr(ptr) }
}

pub fn consuming_function() {
    // For example, this shouldn't be possible in this case
    let prolonged: &'static CStr = get_string_from_host();
    // But this should
    let owned = get_string_from_host().to_owned();
}

1 Ответ

2 голосов
/ 08 апреля 2020

Ваш вопрос и комментарии выложите ваши варианты. В основном это сводится к удовлетворению ожиданий других людей, то есть к правилу наименьшего удивления. Это свидетельствует о возвращении принадлежащего String. Как было сказано ранее, принадлежащий String включает в себя копию (которая будет иметь незначительное влияние на производительность, если только не будет назван gazillion раз в oop)

Я бы настоятельно рекомендовал против raw-указателя - и CStr -reference-решения, которые являются пушками.

Лично я бы go с закрытием, поскольку это реализует базовую c ситуацию: Контекст код, обращающийся к строке, должен переместиться туда, где находится строка; мы не можем допустить, чтобы строка переместилась туда, где контекст (насколько нам известно, даже вызывающая сторона может не контролировать). Решение укупорки должно позволить вам иметь свой торт и есть его: укупорка типа impl Fn(&CStr) -> T может быть |s| s.to_owned(), поэтому with_string_from_host возвращает копию при желании.

...