diff options
author | Vincent Ambo <tazjin@google.com> | 2019-12-21T00·53+0000 |
---|---|---|
committer | Vincent Ambo <tazjin@google.com> | 2019-12-21T00·53+0000 |
commit | fbdc9b1d6009c7b9294542c6935a760a6d5eb819 (patch) | |
tree | 64f0a832f2d98f6703f9d7e66be8dc4e2efdf7ae /fun/defer_rs | |
parent | acdd21f8f4b476d280e6b78dca4023b7aabb4999 (diff) | |
parent | 426780060dee0abb47c85f839943d35a70b0af01 (diff) |
merge(defer.rs): Integrate at //fun/defer_rs r/262
Diffstat (limited to 'fun/defer_rs')
-rw-r--r-- | fun/defer_rs/.gitignore | 3 | ||||
-rw-r--r-- | fun/defer_rs/Cargo.toml | 6 | ||||
-rw-r--r-- | fun/defer_rs/README.md | 53 | ||||
-rw-r--r-- | fun/defer_rs/examples/defer-with-error.rs | 70 | ||||
-rw-r--r-- | fun/defer_rs/examples/defer.rs | 31 | ||||
-rw-r--r-- | fun/defer_rs/examples/undefer.rs | 40 |
6 files changed, 203 insertions, 0 deletions
diff --git a/fun/defer_rs/.gitignore b/fun/defer_rs/.gitignore new file mode 100644 index 000000000000..6aa106405a4b --- /dev/null +++ b/fun/defer_rs/.gitignore @@ -0,0 +1,3 @@ +/target/ +**/*.rs.bk +Cargo.lock diff --git a/fun/defer_rs/Cargo.toml b/fun/defer_rs/Cargo.toml new file mode 100644 index 000000000000..0fcd60373ff7 --- /dev/null +++ b/fun/defer_rs/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "defer" +version = "0.1.0" +authors = ["Vincent Ambo <tazjin@gmail.com>"] + +[dependencies] diff --git a/fun/defer_rs/README.md b/fun/defer_rs/README.md new file mode 100644 index 000000000000..160158d177b8 --- /dev/null +++ b/fun/defer_rs/README.md @@ -0,0 +1,53 @@ +defer in Rust +============= + +After a Hacker News discussion about implementing Go's `defer` keyword in C++, +I stumbled upon [this comment](https://news.ycombinator.com/item?id=15523589) +and more specifically this response to it by "Occivink": + +> There's plenty of one-time cases where you don't want to declare an entire +> class but still enjoy scope-based functions. + +Specificall the "don't want to declare an entire class" suggests that languages +like C++ have high friction for explaining your desired invariant (cleanup is +run when `$thing` is destroyed) to the compiler. + +It seems like most languages either hand-wave this away (*cough* Java *cough*) +or use what seems like a workaround (`defer`). + +Rust has the so-called `Drop` trait, which is a typeclass that contains a single +method with no return value that is run when a variable is dropped (i.e. goes out +of scope). + +This works fine for most general cases - i.e. closing file handlers - but can +get complicated if other use-cases of `defer` are considered: + +* returning an error-value by mutating a reference in the enclosing scope (oh boy) +* deferring a decision about when/whether to run cleanup to the caller + +While thinking about how to do this with the `Drop` trait I realised that `defer` +can actually be trivially implemented in Rust, using `Drop`. + +A simple implementation of `defer` can be seen in [defer.rs](examples/defer.rs), +an implementation using shared mutable state for error returns is in the file +[defer-with-error.rs](examples/defer-with-error.rs) and an implementation that +allows cleanup to be *cancelled* (don't _actually_ do this, it leaks a pointer) +is in [undefer.rs](examples/undefer.rs). + +Whether any of this is actually useful is not up to me to decide. I haven't +actually had a real-life need for this. + +You can run the examples with `cargo run --example defer`, etc. + +## Notes + +* `Drop` is not guaranteed to run in case of panics or program aborts, if you + need support for that check out [scopeguard](https://github.com/bluss/scopeguard) +* `undefer` could be implemented safely by, for example, carrying a boolean that + by default causes execution to happen but can be flipped to disable it + +## Further reading: + +* [The Pain Of Real Linear Types in Rust](https://gankro.github.io/blah/linear-rust/) +* [Go's defer](https://tour.golang.org/flowcontrol/12) +* [Rust's Drop](https://doc.rust-lang.org/std/ops/trait.Drop.html) diff --git a/fun/defer_rs/examples/defer-with-error.rs b/fun/defer_rs/examples/defer-with-error.rs new file mode 100644 index 000000000000..26d56d77cf1b --- /dev/null +++ b/fun/defer_rs/examples/defer-with-error.rs @@ -0,0 +1,70 @@ +// Go's defer in Rust, with error value return. + +use std::rc::Rc; +use std::sync::RwLock; + +struct Defer<F: Fn()> { + f: F +} + +impl <F: Fn()> Drop for Defer<F> { + fn drop(&mut self) { + (self.f)() + } +} + +// Only added this for Go-syntax familiarity ;-) +fn defer<F: Fn()>(f: F) -> Defer<F> { + Defer { f } +} + +// Convenience type synonym. This is a reference-counted smart pointer to +// a shareable, mutable variable. +// Rust does not allow willy-nilly mutation of shared variables, so explicit +// write-locking must be performed. +type ErrorHandle<T> = Rc<RwLock<Option<T>>>; + +/////////////////// +// Usage example // +/////////////////// + +#[derive(Debug)] // Debug trait for some default way to print the type. +enum Error { DropError } + +fn main() { + // Create a place to store the error. + let drop_err: ErrorHandle<Error> = Default::default(); // create empty error + + // Introduce an arbitrary scope block (so that we still have control after + // the defer runs): + { + let mut i = 1; + + // Rc types are safe to clone and share for multiple ownership. + let err_handle = drop_err.clone(); + + // Call defer and let the closure own the cloned handle to the error: + let token = defer(move || { + // do something! + println!("Value is: {}", i); + + // ... oh no, it went wrong! + *err_handle.write().unwrap() = Some(Error::DropError); + }); + + i += 1; + println!("Value is: {}", i); + + // token goes out of scope here - drop() is called. + } + + match *drop_err.read().unwrap() { + Some(ref err) => println!("Oh no, an error occured: {:?}!", err), + None => println!("Phew, everything went well.") + }; +} + +// Prints: +// Value is: 2 +// Value is: 1 +// Oh no, an error occured: DropError! diff --git a/fun/defer_rs/examples/defer.rs b/fun/defer_rs/examples/defer.rs new file mode 100644 index 000000000000..eadac795f8bc --- /dev/null +++ b/fun/defer_rs/examples/defer.rs @@ -0,0 +1,31 @@ +// Go's defer in Rust! + +struct Defer<F: Fn()> { + f: F +} + +impl <F: Fn()> Drop for Defer<F> { + fn drop(&mut self) { + (self.f)() + } +} + +// Only added this for Go-syntax familiarity ;-) +fn defer<F: Fn()>(f: F) -> Defer<F> { + Defer { f } +} + +fn main() { + let mut i = 1; + + // Calling it "token" ... could be something else. The lifetime of this + // controls when the action is run. + let _token = defer(move || println!("Value is: {}", i)); + + i += 1; + println!("Value is: {}", i); +} + +// Prints: +// Value is: 2 +// Value is: 1 diff --git a/fun/defer_rs/examples/undefer.rs b/fun/defer_rs/examples/undefer.rs new file mode 100644 index 000000000000..17ad8a6b5485 --- /dev/null +++ b/fun/defer_rs/examples/undefer.rs @@ -0,0 +1,40 @@ +// Go's defer in Rust, with a little twist! + +struct Defer<F: Fn()> { + f: F +} + +impl <F: Fn()> Drop for Defer<F> { + fn drop(&mut self) { + (self.f)() + } +} + +// Only added this for Go-syntax familiarity ;-) +fn defer<F: Fn()>(f: F) -> Defer<F> { + Defer { f } +} + +// Changed your mind about the defer? +// (Note: This leaks the closure! Don't actually do this!) +fn undefer<F: Fn()>(token: Defer<F>) { + use std::mem; + mem::forget(token); +} + +fn main() { + let mut i = 1; + + // Calling it "token" ... could be something else. The lifetime of this + // controls when the action is run. + let token = defer(move || println!("Value is: {}", i)); + + i += 1; + println!("Value is: {}", i); + + // Oh, now I changed my mind about the previous defer: + undefer(token); +} + +// Prints: +// Value is: 2 |