struct Task {
future: Mutex<Option<BoxFuture<'static, ()>>>,
task_sender: SyncSender<Arc<Task>>,
} struct Task {
future: Mutex<Option<BoxFuture<'static, ()>>>,
task_sender: SyncSender<Arc<Task>>,
}On first glance you're like wtf is going on, but you can derive backwards what is happening without having to look for a discussion on language behavior under the hood. Automagic is nice sometimes but I'm the kind of person that needs to say the each word of an acronym to process it.
Rust is like that in a way. You have a mutex on an option type, the option has a heap allocated future that contains data that can live for the lifetime of the program.
This is clear to me because I don't need to fill in blanks. My memory is terrible, I've forgotten so many things I've learned in the past. I could pick them back up quite quickly but I don't have the little facts ready to go. If i wanted to use that future, I know I need to check the mutex lock, check if the option contains a Some(), etc.
Sure this isn't for everyone, but I'm glad we have a tool like this gaining popularity. I have little interest in studying the arcane knowledge of C++ and sorting out what is current and what is obsolete, then arguing with a 30 year veteran that their technique is 20 years stale.
(defrecord Task [future task-sender])
Probably used something like this: (defn create-task []
(let [future (atom nil)
task-sender (async/chan)]
(->Task future task-sender)))
Not sure it makes sense but a pretty much direct translation as far as it goes. (defstruct Task
(future :type Future)
(task-sender :type Sender))
And have SBCL warn me when I try to jam the wrong type in. (s/def ::future atom?))
(s/def ::task-sender async/chan?)
(s/def ::task
(s/keys :req-un [::future ::task-sender]))
(s/fdef create-task
:ret ::task)
(stest/instrument `create-task)
But then I don't think you'd reach for something like Clojure if static typing is something that you require. type Task struct {
Sender chan Task
}
no?And by reading it I mean I actually read the thing front to back before touching a keyboard. Then I started to experiment with some simple code and after a couple of months I had some actually programs that I use often. Obviously I rewrote them after a year but hey ho, it’s all a learning experience.
I'm gonna make it worse first by substituting the `BoxFuture` type alias with its definition but that makes it easier to explain.
future: Mutex<Option<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>>,
What this means is that the future field stores something that is a:- Dynamically dispatched object (with a V-Table) that implements methods from the Future trait, and returns nothing () = void. (dyn Future<Output = ()>)
- That thing needs to be sendable between Threads (+ Send)
- And must potentially live forever (+ 'static)
- The dynamic dispatch stuff only works with heap allocations because the V-Table pointer is stored in the double wide smart-pointer. In this case we use a Box which means only one thing/thread can hold it at a time (unlike reference counted Rc/Arc).
- That allocation of the Heap allocation must not be moved around in memory. (Pin<...>)
- that thing might not be there (it can be Null), but replacing/taking/putting the thing will be secured by the mutex so that's a nice guarantee for multithreading (Option<...>)
- access to a thing is synchronised via a mutex (Mutex<...>)
So while it looks horrible at first sight, it is simply telling you a lot about the guarantees of that particular type.
Got directed from slog to tokio-tracing for logging, so dove into setting up my basic "--log-level", "--log-format" CLI options I add to every non-trivial program in Rust and ran into...whatever is going on with configuring it (namely, tokio-subscriber seems to encode all the configuration information into types, so you can't just return a Subscriber or Subscriber builder from a function and then add it as a logger...the documentation gives no hint on what trait or type I'm meant to return here).
Since rustc uses tracing I really hope it's the first...
However the type explicitness is, IMO, one of its strengths. It lets you build up types that e.g. in C++ we're not a given, had properties about the behavior buried in docs, etc.
Thus even newbies get to bump into it quite fast.
The chapter that example is from includes the disclaimer:
> In this section, we'll cover the underlying structure of how Futures and asynchronous tasks are scheduled. If you're only interested in learning how to write higher-level code that uses existing Future types and aren't interested in the details of how Future types work, you can skip ahead to the async/await chapter.