2015-04-30 25 views
9

W programie Go można użyć słowa kluczowego defer w celu wykonania funkcji po powrocie bieżącej funkcji, podobnie do tradycyjnego słowa kluczowego finally w innych językach. Jest to przydatne do oczyszczania stanu, niezależnie od tego, co dzieje się w ciele funkcji. Oto przykład z blogu Go:Golangopodobny odroczenie w Rust

Jak można tę funkcjonalność uzyskać w Rust? Wiem o RAII, ale w moim konkretnym przypadku państwo znajduje się w systemie zewnętrznym. Piszę test, który zapisuje klucz do magazynu klucz-wartość, i muszę upewnić się, że został usunięty na końcu testu, niezależnie od tego, czy asercje w teście powodują panikę.

Znalazłem this Gist, ale nie wiem, czy jest to zalecane podejście. Niebezpieczny destruktor jest niepokojący.

Istnieje również this issue w repozytorium Rust GitHub, ale ma on trzy lata i nie jest już zbyt istotny.

+0

I don nie znają dużo rdzy, ale czy nie wystarczy funkcja, która wystarczy do zamknięcia? –

+1

Oczywiście, zwykłe pliki otwierane przy użyciu libstd Rusta zamkną się same w swoim kleju upuszczającym. – bluss

Odpowiedz

16

(e: nie przegap bluss na odpowiedź, a ich scopedguard paka, poniżej).

Prawidłowe sposobem osiągnięcia tego celu poprzez kod, który działa w destructor, podobnie jak defer! makro połączyć się robi . Dla czegoś więcej niż testów ad-hoc polecam napisać typ uchwytu z odpowiednim destruktorem, np. jeden wchodzi w interakcję z std::sync::Mutex poprzez jego typ MutexGuard (zwrócony przez lock): nie ma potrzeby wywoływania unlock na samym muteksie. (Wyraźne podejście "uchwyt z destruktorem" jest także bardziej elastyczne: ma zmienny dostęp do danych, podczas gdy odroczone podejście może nie być możliwe, ze względu na silne kontrole aliasingu Rusta.)

W każdym razie to makro jest teraz (dużo!) ulepszony z powodu ostatnich zmian, w szczególności, pnkfelix's sound generic drop work, który usuwa konieczność dla #[unsafe_destructor]. Bezpośrednia zmiana będzie:

struct ScopeCall<F: FnMut()> { 
    c: F 
} 
impl<F: FnMut()> Drop for ScopeCall<F> { 
    fn drop(&mut self) { 
     (self.c)(); 
    } 
} 

macro_rules! defer { 
    ($e:expr) => (
     let _scope_call = ScopeCall { c: || ->() { $e; } }; 
    ) 
} 

fn main() { 
    let x = 42u8; 
    defer!(println!("defer 1")); 
    defer!({ 
     println!("defer 2"); 
     println!("inside defer {}", x) 
    }); 
    println!("normal execution {}", x); 
} 

wyjściowa:

normal execution 42 
defer 2 
inside defer 42 
defer 1 

Chociaż byłoby składniowo ładniejszy jak:

macro_rules! expr { ($e: expr) => { $e } } // tt hack 
macro_rules! defer { 
    ($($data: tt)*) => (
     let _scope_call = ScopeCall { 
      c: || ->() { expr!({ $($data)* }) } 
     }; 
    ) 
} 

(tt hack jest konieczne ze względu na #5846.)

Użycie standardowego tt ("drzewa tokenów") pozwala na wywołanie go bez wewnętrznego wewnętrznego { ... }, gdy istnieje wiele instrukcji (tj. zachowuje się bardziej jak „normalnej” struktury przepływu sterowania):

defer! { 
    println!("defer 2"); 
    println!("inside defer {}", x) 
} 

Również dla maksymalnej elastyczności o co kod odroczony można zrobić z przechwyconych zmiennych, można użyć FnOnce zamiast FnMut:

struct ScopeCall<F: FnOnce()> { 
    c: Option<F> 
} 
impl<F: FnOnce()> Drop for ScopeCall<F> { 
    fn drop(&mut self) { 
     self.c.take().unwrap()() 
    } 
} 

Będzie to również wymagało skonstruowania modelu ScopeCall z wartością Some wokół wartości dla c.Taniec Option jest wymagany, ponieważ wywołanie FnOnce przenosi prawa własności, co nie jest możliwe bez tego za self: &mut ScopeCall<F>. (Robi to jest OK, ponieważ destruktor wykonuje się tylko raz.)

W sumie: (. Ta sama moc jak oryginał)

struct ScopeCall<F: FnOnce()> { 
    c: Option<F> 
} 
impl<F: FnOnce()> Drop for ScopeCall<F> { 
    fn drop(&mut self) { 
     self.c.take().unwrap()() 
    } 
} 

macro_rules! expr { ($e: expr) => { $e } } // tt hack 
macro_rules! defer { 
    ($($data: tt)*) => (
     let _scope_call = ScopeCall { 
      c: Some(|| ->() { expr!({ $($data)* }) }) 
     }; 
    ) 
} 

fn main() { 
    let x = 42u8; 
    defer!(println!("defer 1")); 
    defer! { 
     println!("defer 2"); 
     println!("inside defer {}", x) 
    } 
    println!("normal execution {}", x); 
} 

+1

wygląda na to, że 'tt hack' był [naprawiony] (https://github.com/rust-lang/rust/pull/34908) –

8

używam następujących do strażnika zakres. Wykorzystuje Deref cech w celu dostarczenia wspólnych & zmienny dostęp do strzeżonego wartości, bez przenoszenia go na zewnątrz (które unieważniają strażnika!)

Moje przypadek użycia jest prawidłowo resetowania terminala, gdy program wychodzi, nawet jeśli w panikę:

extern crate scopeguard; 
use scopeguard::guard; 

// ... terminal setup omitted ... 

// Use a scope guard to restore terminal settings on quit/panic 
let mut tty = guard(tty, |tty| { 
    // ... I use tty.write() here too ... 
    ts::tcsetattr(tty.as_raw_fd(), ts::TCSANOW, &old_attr).ok(); 
}); 
game_main(&mut tty).unwrap(); // Deref coercion magic hands off the inner &mut TTY pointer here. 

Moduł scopeguard.rs:

use std::ops::{Deref, DerefMut}; 

pub struct Guard<T, F> where 
    F: FnMut(&mut T) 
{ 
    __dropfn: F, 
    __value: T, 
} 

pub fn guard<T, F>(v: T, dropfn: F) -> Guard<T, F> where 
    F: FnMut(&mut T) 
{ 
    Guard{__value: v, __dropfn: dropfn} 
} 

impl<T, F> Deref for Guard<T, F> where 
    F: FnMut(&mut T) 
{ 
    type Target = T; 
    fn deref(&self) -> &T 
    { 
     &self.__value 
    } 

} 

impl<T, F> DerefMut for Guard<T, F> where 
    F: FnMut(&mut T) 
{ 
    fn deref_mut(&mut self) -> &mut T 
    { 
     &mut self.__value 
    } 
} 

impl<T, F> Drop for Guard<T, F> where 
    F: FnMut(&mut T) 
{ 
    fn drop(&mut self) { 
     (self.__dropfn)(&mut self.__value) 
    } 
} 

jest to obecnie paka scopeguard on crates.io.

+0

Chciałbym móc przyjąć obie odpowiedzi, bo to jest naprawdę niesamowite i prawdopodobnie to, co zrobię faktycznie używać. Dziękuję Ci! –