2016-08-25 50 views
1

有沒有一種方法(生鏽)發送一個可變的借來的自我回調沒有mem::replace黑客我在下面的MWE中使用?我正在使用防鏽(1.11.0)。回調可變自我

use std::mem; 

trait Actable { 
    fn act(&mut self); 
} 

// Not Cloneable 
struct SelfCaller { 
    message: String, 
    callback: Box<FnMut(&mut SelfCaller)>, 
    // other stuff 
} 

impl Actable for SelfCaller { 
    fn act(&mut self) { 
     fn noop(_: &mut SelfCaller) {} 
     let mut callback = mem::replace(&mut self.callback, Box::new(noop)); 
     callback(self); 
     mem::replace(&mut self.callback, callback); 
    } 
} 

impl Drop for SelfCaller { 
    fn drop(&mut self) {/* unimiportant to the story */} 
} 

fn main() { 
    fn change(messenger: &mut SelfCaller) { 
     messenger.message = "replaced message".to_owned(); 
    } 

    let mut messenger = SelfCaller { 
     message: "initial message".to_owned(), 
     callback: Box::new(change), 
    }; 

    messenger.act(); 

    println!("{}", &messenger.message); 
} 

Play

回答

2

不,沒有辦法,,因爲這樣做不安全。這是一個演示原因的示例(需要夜間編譯器)。

#![feature(fn_traits)] 
#![feature(unboxed_closures)] 

use std::mem; 

trait Actable { 
    fn act(&mut self); 
} 

struct SelfCaller { 
    message: String, 
    callback: Box<FnMut(&mut SelfCaller)>, 
} 

impl Actable for SelfCaller { 
    fn act(&mut self) { 
     let mut callback: &mut Box<FnMut(&mut SelfCaller)> = unsafe { mem::transmute(&mut self.callback) }; 
     println!("calling callback"); 
     callback(self); 
     println!("called callback"); 
    } 
} 

struct Callback; 

impl Drop for Callback { 
    fn drop(&mut self) { 
     println!("Callback dropped!"); 
    } 
} 

impl<'a> FnOnce<(&'a mut SelfCaller,)> for Callback { 
    type Output =(); 

    extern "rust-call" fn call_once(mut self, args: (&mut SelfCaller,)) { 
     self.call_mut(args) 
    } 
} 

impl<'a> FnMut<(&'a mut SelfCaller,)> for Callback { 
    extern "rust-call" fn call_mut(&mut self, (messenger,): (&mut SelfCaller,)) { 
     println!("changing callback"); 
     messenger.callback = Box::new(|messenger| {}); 
     println!("changed callback"); 
     messenger.message = "replaced message".to_owned(); 
    } 
} 

fn main() { 
    let change = Callback; 

    let mut messenger = SelfCaller { 
     message: "initial message".to_owned(), 
     callback: Box::new(change), 
    }; 

    messenger.act(); 

    println!("{}", &messenger.message); 
} 

這個程序的輸出是:

calling callback 
changing callback 
Callback dropped! 
changed callback 
called callback 
replaced message 

好了,這是怎麼回事?首先,我已經寫了act的執行SelfCaller這樣一種方式,我可以調用回調沒有mem::replace,使用mem::transmute讓編譯器生成一個從self斷開連接的新生命期。

然後,我已經寫一個回調(使用結構Callback,因爲我需要同時實現FnMutDrop以證明該問題的類型),其通過改變其callback構件變異的SelfCaller。這具有的效果,從而減少了以前的回調,這是當前正在執行的回調!如果Callback包含數據成員,嘗試讀取它們會導致未定義的行爲,因爲它們現在處於釋放內存中(我們丟棄了整個Box)。


順便說一句,在使用mem::replace你的代碼,回調不能改變的回調,因爲你的回撥電話結束後恢復回調。

+0

謝謝!這很有幫助。但就我而言,回調成員是不可改變的。如果SelfCaller.callback成員從回調中隱藏,可以接受這樣做嗎? –

+0

是的,如果'callback'成員不公開並且沒有改變它的公共方法,那麼在整個生命週期中作弊是安全的。請務必在代碼中留下一兩條評論來記錄'callback'成員不應該被用戶修改的事實,以便將來沒有人添加mutator方法! –

2

沒有,這是不可能用你的代碼。如果可能的話,您可以輕鬆構建一個破壞內存安全的例子,例如通過訪問釋放的內存(,這是留給練習讀者)。

您可以考慮FnMut是否真的需要SelfCaller的所有字段。如果沒有,你可以通過(希望很少)單個字段作爲參數。如果沒有,您可以創建另一種類型(我們稱之爲Inner),其中包含對回調非常重要的所有字段並將其傳遞給該函數。

2

如果您不需要舉債的環境回調,您可以使用一個函數,而不是關閉:

trait Actable { 
    fn act(&mut self); 
} 

struct SelfCaller { 
    message: String, 
    callback: fn(&mut SelfCaller), 
} 

impl Actable for SelfCaller { 
    fn act(&mut self) { 
     (self.callback)(self); 
    } 
} 

fn main() { 
    fn change(messenger: &mut SelfCaller) { 
     messenger.message = "replaced message".to_owned(); 
    } 

    let mut messenger = SelfCaller { 
     message: "initial message".to_owned(), 
     callback: change, 
    }; 

    messenger.act(); 

    println!("{}", &messenger.message); 
} 
+0

哎呀!我忘了讓SelfCaller實現Drop! –