Есть ли способ выразить один и тот же общий тип с разными сроками жизни?

Рассмотрим следующую (неполную) сигнатуру функции:

unsafe fn foo<'a, T: 'a>(func: impl FnOnce() -> T + 'a) -> ...

Есть ли способ (небезопасно, конечно) transmute функцию ввода, чтобы она стала impl FnOnce() -> S + 'static, где S того же типа, что и T, но с S: 'static.

Я знаю, что можно преобразовать границы времени жизни самого замыкания, используя упакованную черту (FnBox), а затем вызывая преобразование в коробке. Однако это не влияет на тип возвращаемого значения (T). Насколько я понимаю, T: 'a и T: 'static - это разные типы в соответствии с системой типов. Так что мне интересно, можно ли вообще выразить это в Rust.

Я полагаю, что подпись должна выглядеть так (игнорируя ограничения времени жизни самого закрытия):

unsafe fn<'a, T, S>(func: impl FnOnce() -> T) -> impl FnOnce() -> S
where
    T: 'a,
    S: 'static`

но тогда как вы вызываете эту функцию без указания, что T и S идентичны, за исключением их срока службы.

Заявление об ограничении ответственности. Я знаю, что возиться с ограничениями времени жизни, как правило, не рекомендуется, однако это касается создания потоков, в которых ограничения времени жизни применяются другими способами.


person Oliver    schedule 20.09.2018    source источник
comment
Я думаю, что на самом деле вам нужна подпись: unsafe fn foo<'a, T>(func: impl FnOnce() -> T + 'a) -> impl FnOnce() -> T + 'static.   -  person Peter Hall    schedule 20.09.2018
comment
Но я не уверен, что это сработает, потому что конкретный тип impl в аргументе определяется вызывающим, а impl в позиции возврата определяется функцией. Но каким-то образом они должны быть одинаковыми? Я не думаю, что ты сможешь это сделать.   -  person Peter Hall    schedule 20.09.2018
comment
И если вы попытаетесь трансмутировать, вам нужно будет сказать, в какой тип трансмутировать, но вы не можете назвать тип замыкания.   -  person Peter Hall    schedule 20.09.2018
comment
Это настолько необычная просьба, что я должен представить, что вы упустили какой-то другой способ достичь того, что вы действительно пытаетесь сделать. Почему бы вам не задать вопрос об основной проблеме (порождение потоков, в которых ограничения времени жизни применяются другими способами)? Например, потоки с заданной областью могут решить множество проблем на протяжении всего срока службы (соответствующие вопросы и ответы).   -  person trentcl    schedule 20.09.2018
comment
Это такая необычная просьба ... - согласился. См. Резюме в конце моего ответа.   -  person Peter Hall    schedule 20.09.2018
comment
Возможный дубликат Отказ от ограничений срока службы?   -  person trentcl    schedule 19.10.2018


Ответы (1)


Если бы вы просто хотели сделать это с простыми типами, это было бы несложно, но есть ряд препятствий на пути к тому, что вы пытаетесь сделать. Я объясню их в том порядке, в котором я их встречал, пытаясь найти ответ.

Во-первых, вы не можете реализовать это с impl trait типами, потому что сама функция должна выбрать конкретную реализацию, которую она собирается вернуть, но это невозможно, потому что реализация всегда будет основана на выборе типа аргумента func из звонящий. Это исключает «естественный» тип:

unsafe fn foo<'a, T>(func: impl FnOnce() -> T + 'a) -> impl FnOnce() -> T + 'static

И приводит к чему-то большему:

unsafe fn foo<'a, T, F, G>(func: F) -> G
where
    F: FnOnce() -> + 'a,
    G: FnOnce() -> + 'static,

Но как вызывающий абонент узнает, какой тип G должен быть?

Если вы попытаетесь использовать mem::transmute, чтобы обмануть программу проверки заимствований, вам нужно будет указать ей, во что преобразовать. Проблема в том, что вы знаете только тип (например) impl FnOnce() -> T + 'static, но вы не можете записать конкретный тип замыкания, так что это тоже не сработает.

Так что я думаю, что ответ - Box результат. Это может показаться неудовлетворительным, но становится еще хуже! Хотя создать Box<dyn FnOnce()> можно, но в настоящее время невозможно вызвать эту функцию позже , что означает, что вам придется пойти на другой компромисс, а именно перейти с FnOnce на Fn.

use std::mem;

unsafe fn foo<'a, T>(func: impl Fn() -> T + 'a) -> Box<dyn Fn() -> T + 'static> {
    let boxed: Box<dyn Fn() -> T + 'a> = Box::new(func);
    mem::transmute(boxed)
}

Таким образом, возможно, вам следует сделать шаг назад и найти другую проблему, которую нужно решить, вместо этой.

person Peter Hall    schedule 20.09.2018
comment
Спасибо за ваш ответ, использование упакованных закрытий в качестве объектов-трейтов достаточно хорошо работает для замыкания, но моя проблема в первую очередь связана с типом, возвращаемым закрытием. Мне также действительно не нужно так сильно обманывать средство проверки заимствований, как систему типов, как систему типов, поскольку границы времени жизни оказываются неразрывно связанными с (универсальным) типом после того, как они установлены. Однако вы, вероятно, правы, что на самом деле нет хорошего способа сделать это. - person Oliver; 20.09.2018
comment
Мои намерения заключаются в том, чтобы реализовать потоки с ограниченной областью видимости. Подпись для порождения потока stdlib - pub fn spawn<F, T>(f: F) -> JoinHandle<T> where F: FnOnce() -> T, F: Send + 'static, T: Send + 'static, и я попытался найти способ обмануть систему типов, позволив мне использовать закрывающий / возвращаемый тип с менее строгими границами времени жизни. После тщательного изучения некоторых реализаций потоков с заданной областью видимости я обнаружил, что им приходится преодолевать множество препятствий, чтобы заставить такие вещи работать, и я надеялся найти более простой способ. - person Oliver; 20.09.2018