Type-Driven design in Rust
Tags: rust, typedriven
Published: 2024-04-12 16:04:41

Hi. Good night. This is Helio, a Chinese student, who are learning Rust lang now. Today we gonna talk about type-driven design in Rust. This course is spring by ["Type-Driven API Design in Rust" by Will Crichton](https://www.youtube.com/watch?v=bnnacleqg6k&list=PLUraG4qyd-nNgN4TuXqT50q0t5VUB51mQ), you can goto youtube to seek more details about type-driven design.

Let's look at a piece of python code now:

![Bounded](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vy9nyg04bid3tykxjo8z.png)

This code will generate a progress bar like this:

![Bounded output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mfsy2ojwq6ikn7sye1ji.png)

So what will happen when we using a unbounded list like:

![unbounded](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6d66axr4uic4qhrob2tl.png)

The output will be like below one:

![unbounded output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t260ugsznz732g6n6f7r.png)

We find that if tqdm receive a bounded array, the progress bar will display, and the normal things like item/s also shown in the screen, but when tqdm receive a unbounded array like `count()`, it will only print the normal things like item/s, so let we design a rust lib called Progress. We will start at `cargo new progress`.

## Start here
After enter `cargo new progress`, we create the project, and we run:

![Hello World](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3q9ejwheyk67ow6ple8h.png)

Nice. The first step we should think how to create a progress bar like tqdm, so we write down the following code:

![Init version](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hq9n9w5spp3mp77u2ik7.png)

We noticed that the output is ugly, generally we will improve this, we add `static CLEAR: &str = "\x1B[2J\x1B[1;1H";` at the top of the code, and output will be more fine:

![Init Output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qch8njygw5i7o746v6gx.png)

But once we need a progress bar, we also need to write this piece of code again and again, so we decided to design it to a function:

![Function Version](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wgxifaiq73o866fk2aey.png)

This output exactly equal to previous one. But if we don't use the `Vec<i32>`, instead, we use `u32`, what will happend? wrong! So we normally make the `Vec<i32>` a `Vec<T>`. But if we look one more ahead, we will use the `Iterator` instead of `Vec<T>`, so the code becomes:

![Iterator Version](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t8gbib2r42fa71wymywo.png)

Also the output will be equal to previous one. So if we want to go deep with the code, we should impl this one:
```rust
for n in Progress::new(v.iter()) {
    expensive_cal()
}
```

All right, let we create a struct named `Progress`, and define it like below one:

![Struct](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0pb1qp2ktstfgztqegz5.png)

Ok, what if we design Progress use like this?

```rust
for n in v.iter().progress() {
    expensive_cal()
}
```

We need to impl something interesting:
```rust
trait ProgressIteratorExt<Iter> {
    fn progress(self) -> Progress<Iter>;
}

impl<Iter> ProgressIteratorExt<Iter> for Iter {
    fn progress(self) -> Progress<Iter> {
        Progress { iter: self, i: 1 }
    }
}
```

![Ext](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nfkbvzn8tza8orwyctkb.png)

But something went wrong, because we impl `progress` for every type of T, i32/u32/... Code like below one will occur errors:
```rust
let a = 10;
let p = a.progress();
for _ in p { } // Here

`{integer}` is not an iterator
the trait `Iterator` is not implemented for `{integer}`, which is required by `Progress<{integer}>: IntoIterator`
```

The right version is like this:
```rust
trait ProgressIteratorExt<Iter> {
    fn progress(self) -> Progress<Iter>;
}

impl<Iter> ProgressIteratorExt<Iter> for Iter
where
    Iter: Iterator,
{
    fn progress(self) -> Progress<Iter> {
        Progress { iter: self, i: 1 }
    }
}
```

The output `***` is too simple, we will add delims ('[', ']') to the code, so we update our struct:
```rust
struct Progress<Iter> {
    iter: Iter,
    i: usize,
    delims: (char, char),
    bound: Option<usize>,
}
```

The entire code will be like this:
```rust
#[allow(unused_variables)]
use std::{thread::sleep, time::Duration};

static CLEAR: &str = "\x1B[2J\x1B[1;1H";

struct Progress<Iter> {
    iter: Iter,
    i: usize,
    delims: (char, char),
    bound: Option<usize>,
}

impl<Iter> Progress<Iter>
where
    Iter: Iterator,
{
    pub fn new(iter: Iter) -> Self {
        Progress {
            iter,
            i: 1,
            delims: ('[', ']'),
            bound: None,
        }
    }
}

impl<Iter> Iterator for Progress<Iter>
where
    Iter: Iterator,
{
    type Item = Iter::Item;
    fn next(&mut self) -> Option<Self::Item> {
        if let Some(next) = self.iter.next() {
            match self.bound {
                Some(bound) => println!(
                    "{}{}{}{}{}",
                    CLEAR,
                    self.delims.0,
                    "*".repeat(self.i),
                    " ".repeat(bound - self.i),
                    self.delims.1
                ),
                None => println!("{}{}", CLEAR, "*".repeat(self.i),),
            }
            self.i += 1;
            return Some(next);
        } else {
            return None;
        }
    }
}

trait ProgressIteratorExt<Iter> {
    fn progress(self) -> Progress<Iter>;
}

impl<Iter> ProgressIteratorExt<Iter> for Iter
where
    Iter: Iterator,
{
    fn progress(self) -> Progress<Iter> {
        Progress {
            iter: self,
            i: 1,
            delims: ('[', ']'),
            bound: None,
        }
    }
}

impl<Iter> Progress<Iter>
where
    Iter: ExactSizeIterator,
{
    pub fn with_bound(mut self) -> Progress<Iter> {
        self.bound = Some(self.iter.len());
        Progress {
            iter: self.iter,
            i: 1,
            delims: self.delims,
            bound: self.bound,
        }
    }
}

fn expensive_cal(_n: &i32) {
    sleep(Duration::from_secs(1));
}

fn main() {
    let v = vec![1, 2, 3];
    for n in v.iter().progress().with_bound() {
        expensive_cal(n)
    }
}

```

Ok, the output is `[***]`, the same as we think. We can also make a function called `with_delims`:
```rust
impl<Iter> Progress<Iter>
where
    Iter: ExactSizeIterator,
{
    pub fn with_delims(mut self, delims: (char, char)) -> Progress<Iter> {
        self.delims = delims;
        Progress {
            iter: self.iter,
            i: 1,
            delims: self.delims,
            bound: self.bound,
        }
    }
}
```

The output will becomes `(***)`, right? What if we apply `with_delims` to unbounded array if the trait `ExactSizeIterator` not exist? Nothing will happen, even if the user config the delims. Ok.
```rust
for _ in (0..).progress().with_delims(('(', ')')) {
    expensive_cal()
}
```

The last concept I want to show you is `Type-State`, we write the code like this one:

![Finally Version](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ks7w49774y9jsi9w8bix.png)

The `display` will be impl like this one:

![Finally Version](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7mg52ovs4rw9elsrn13r.png)

The rest part:

![Finally Version](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eg1kcn1oe5nrxwqotoad.png)

Finally we impl the `Type-State` version of Progress.