2016-12-04 3 views
3

Я храню замыкание в структуры, как это:Вызов Stack-Обозначенная Закрытие хранящегося в структуре в Rust

#[derive(Clone)] 
struct S<'a> { 
    func: &'a FnOnce() -> u32 
} 

fn main() { 
    let s = S { func: &|| 0 }; 
    let val = (s.func)(); 
    println!("{}", val); 
} 

Когда я компилирую, s.func не может быть перемещен, чтобы выполнить сам. Я понимаю, почему он не может быть перемещен (а именно, что это только ссылка и что его размер неизвестен во время компиляции), но не знаю, почему он вообще перемещается - это просто из-за того, что замыкания реализуются через черты?

Вот сообщение об ошибке:

error[E0161]: cannot move a value of type std::ops::FnOnce() -> u32: 
the size of std::ops::FnOnce() -> u32 cannot be statically determined 
--> main.rs:8:15 
    | 
8 |  let val = (s.func)(); 
    |    ^^^^^^^^ 

error[E0507]: cannot move out of borrowed content 
--> main.rs:8:15 
    | 
8 |  let val = (s.func)(); 
    |    ^^^^^^^^ cannot move out of borrowed content 

error: aborting due to 2 previous errors 

ли это единственный способ решить эту проблему, чтобы сохранить закрытия в куче (с помощью Box<FnOnce() -> u32>)? И почему вызов закрывает его? Предположительно, вызов его не мутирует сама функция.

ответ

5

Закрытие перемещается, потому что FnOnce::call_once принимает self по стоимости. Этот договор гарантирует гарантию того, что функция не будет вызываться более одного раза.

Если вы действительно вызываете закрытие не более одного раза, и вы хотите использовать черту FnOnce, тогда ваша структура должна взять на себя ответственность за это закрытие (и вам нужно будет сделать вашу структуру общей для типа закрытия) , Обратите внимание, что вызов закрытия будет вытеснять замыкание из вашей структуры, тем самым аннулируя всю структуру; вы можете обойти это, обернув FnOnce в Option и take, перед тем как позвонить ему из замка Option.

Если вы могли бы называть закрытие более одного раза, вы не хотите владеть закрытием или не хотите, чтобы ваша структура была создана по типу закрытия, тогда вы должны использовать либо Fn, либо FnMut. Fn::call принимает self по ссылке и FnMut::call_mut принимает self по изменяемой ссылке. Поскольку оба принимают ссылки, вы можете использовать с ними объекты признаков.

+0

Это помогает загружать, спасибо. Я закончил использование 'Fn' вместо' FnOnce', потому что я хотел создать экземпляр const структуры, но весь этот ответ помогает тонне понять все это. – cderwin

1

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

Можно использовать FnOnce и до сих пор не имеет S родовые о закрытии, но это требует некоторой работы, чтобы установить вещи так, что замыкание не может быть, возможно, вызывается более чем один раз:

  • в закрытие должно храниться в Option, поэтому его содержимое может быть «украдено», а Option заменено на None (эта часть гарантирует, что крышка не будет вызываться дважды);
  • Придумайте черту, которая знает, как украсть закрытие из опции и вызвать ее (или сделать что-то еще, если закрытие уже было украдено);
  • хранит ссылку на объект-объект в S - это позволяет использовать тот же тип S на разных затворах, не будучи общим по типу закрытия.

Результат выглядит следующим образом:

trait Callable { 
    fn call_once_safe(&mut self, default: u32) -> u32; 
} 

impl<F: FnOnce() -> u32> Callable for Option<F> { 
    fn call_once_safe(&mut self, default: u32) -> u32 { 
     if let Some(func) = self.take() { 
      func() 
     } else { 
      default 
     } 
    } 
} 

struct S<'a> { 
    func: &'a mut Callable 
} 

impl<'a> S<'a> { 
    pub fn invoke(&mut self) -> u32 { 
     self.func.call_once_safe(1) 
    } 
} 

fn main() { 
    let mut s = S { func: &mut Some(|| 0) }; 
    let val1 = s.invoke(); 
    let val2 = s.invoke(); 
    println!("{} {}", val1, val2); 
} 

Единственное место, которое знает подробности о закрытии является реализация Callable для конкретного Option<F>, генерируется для каждого закрытия и ссылается виртуальные таблицы в &mut Callable указатель жира, созданный при инициализации func в S.

Смежные вопросы