about summary refs log tree commit diff
path: root/fun/defer_rs
diff options
context:
space:
mode:
authorVincent Ambo <tazjin@google.com>2019-12-21T00·53+0000
committerVincent Ambo <tazjin@google.com>2019-12-21T00·53+0000
commitfbdc9b1d6009c7b9294542c6935a760a6d5eb819 (patch)
tree64f0a832f2d98f6703f9d7e66be8dc4e2efdf7ae /fun/defer_rs
parentacdd21f8f4b476d280e6b78dca4023b7aabb4999 (diff)
parent426780060dee0abb47c85f839943d35a70b0af01 (diff)
merge(defer.rs): Integrate at //fun/defer_rs r/262
Diffstat (limited to 'fun/defer_rs')
-rw-r--r--fun/defer_rs/.gitignore3
-rw-r--r--fun/defer_rs/Cargo.toml6
-rw-r--r--fun/defer_rs/README.md53
-rw-r--r--fun/defer_rs/examples/defer-with-error.rs70
-rw-r--r--fun/defer_rs/examples/defer.rs31
-rw-r--r--fun/defer_rs/examples/undefer.rs40
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