The First Naive Brain
With the first building blocks in place we might be able to sketch our first version of a Brain.
The first part we need to define is the struct that will contain the list of Thoughts
that are about to be processed. We wrap them into an Option
to distinguesh the items that require processing from the ones that are already finished - so no need to further think on them ...
struct Brain {
/// the list of `Thoughts`s that require processing
thoughts: Vec<Option<Thought>>,
}
Now we can implement a function that is able to take a Future
, wrap it into a Thought
and push it to the list.
impl Brain {
/// Add a new `Future` to the [Brain], so it can be processed
fn think_on(&mut self, thinkable: impl Future<Output = ()> + 'static) {
// ensure the given Future is getting a fixed position on the HEAP
let thinkable = Box::pin(thinkable);
// create the Thought
let thought = Thought {
thinkable,
};
// push the Thought to the list of thoughts to think on
self.thoughts.push(Some(thought));
}
}
Finally the Brain requires a function that allows processing of the list of Thought
's. This function will iterate over the items of Brain::thoughts
and will call the poll
function of the Future
contain in the respective Thought
. If this polling yields a Poll::Pending
state the Thought
will be kept in place of the list and is polled again at the next cycle.
impl Brain {
/// Do the actual thinking - check for Thoughts that are waiting to be
/// processed and drive them to completion
fn do_thinking(&mut self) {
// run through the list of Thoughts that require thinking
for maybe_thought in self.thoughts.iter_mut() {
if let Some(thought) = maybe_thought.take() {
// polling the Future requires some kind of Context, we will
// discuss this in the next chapter
if let Poll::Pending = thought.thinkable.as_mut().poll(cx) {
// as long as the state is Poll::Pending we put the
// the Thought back in place for the next round
*maybe_thought = Some(thought);
}
}
}
}
}
The actual first sketch of the Brain
has several flaws. One of them, for example, is that the poll
function of the Future
requires a Context
to be passed. Without having this in place the code will actually not compile.
However, before dealing with the different challenges of the above coude let's have a look how we would actually use the Brain
.
As a first step we will define a Future
that returns a constant value after it has been polled for a fixed number of tries. Nothing really asynchronous here, you are totally right, but let's start simple.
struct GiveNumberFuture {
give_after_tries: u32,
current_tries: u32,
}
impl Future for GiveNumberFuture {
type Output = u32;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
println!("polled {} time(s)", this.current_tries + 1);
if this.give_after_tries > this.current_tries + 1 {
this.current_tries += 1;
Poll::Pending
} else {
Poll::Ready(20)
}
}
}
This Future
does indeed return a value. So we need to embed it into a Future
that does not return any value and can be spawned to the Brain
. The most simple way to do so would be the creation of an async
function that does not return any value and await
the result of the GiveNumberFuture
in it like so:
async fn main_thought() {
let future = GiveNumberFuture {
give_after_tries: 10,
current_tries: 0,
};
let number = future.await;
println!("waited for {}", number);
}
Within the main
function we can now create our Brain
, tell it to think on the main thought which will ultimately wait for the GiveNumberValue
to yield it's result.
fn main() {
println!("Hello, world!");
let mut brain = Brain {
thoughts: Vec::new(),
};
brain.think_on(main_thought());
loop {
brain.do_thinking();
}
}
Assuming the first sketch of our Brain
would already compile and run it would create the following output:
Hello, world!
polled 1 time(s)
polled 2 time(s)
polled 3 time(s)
polled 4 time(s)
polled 5 time(s)
polled 6 time(s)
polled 7 time(s)
polled 8 time(s)
polled 9 time(s)
polled 10 time(s)
waited for 20
The Issues of the Naive Brain
- The missing
Context
hindering it to compile - and what is it used for by the way? - The usage of a
Vec
to store theThought
's may grow endlessly without further handling. - The
Brain
requires mutable access to allow adding of newThought
's and processing them.
Let's tackle them one by one in the next chapters.