(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);
}
I don nie znają dużo rdzy, ale czy nie wystarczy funkcja, która wystarczy do zamknięcia? –
Oczywiście, zwykłe pliki otwierane przy użyciu libstd Rusta zamkną się same w swoim kleju upuszczającym. – bluss