I followed this YouTube video to learn “Type-Driven API Design”.
This note was created during review so that I could extract/summarize knowledge I didn’t know before.
In this video, the author want to implement progess
function for a type which is an associated type Item
of iterator.
By using impl<T> MyTrait for T
, you can implement the trait for all types in its scope.
In other words, you can implement fluent interfaces to 3rd party libraries by trait for generic type T
.
struct Progress<T, U> {
iterator: T,
i: usize,
bound: U,
}
trait ProgressIteratorExt: Sized {
fn progress(self) -> Progress<Self, Unbounded>;
}
impl<T> ProgressIteratorExt for T {
fn progress(self) -> Progress<Self, Unbounded> {
Progress::new(self)
}
}
By v.iter().progress()
, He created a new Progress
object which contains iterator.
Suppose you are Python programmer.
You can not implement progress
for the returned type of iter
unless you change the implementation of iter
.
progress
returns the type “unbounded Progress” Progress<Self, Unbounded>
, and if we want to create a bounded Progress, with_bound
turns Unbounded
into Bounded
.
It was described in the official document (19.2).
Let’s look at the trait std::iter::ExactSizeIterator
:
pub trait ExactSizeIterator: Iterator {
fn len(&self) -> usize { ... }
fn is_empty(&self) -> bool { ... }
}
The first line (trait definition) says, “if you want to implement this ExactSizeIterator
trait, the struct must have Iterator
trait also”, or “you can implement ExactSizeIterator
only the the struct is implemented Iterator
”.
This notation can be regarded as trait inheritance, but it just inherits a specification (trait), not implementations.
When you want to implement Iterator trait for your type, you need to define what the associated type Item
is and how next
method behaves (because next
is the requried method as its documentation says).
impl<T> Iterator for Progress<T>
where T: Iterator {
type Item = T::Item;
fn next(&mut self) -> Option<Self::Item> {
println!("{}{}", CLEAR, "*".repeat(self.i));
self.i += 1;
self.iterator.next()
}
}
\x1B[2J\x1B[1;1H
const CLEAR: &str = "\x1B[2J\x1B[1;1H";
println!("{}", CLEAR);
This is called ANSI escape code.
\x1b
is ASCII for ESCAPE (ESC key), and [2J
is control sequence to clear entire display.\[1;1H
is also a control sequence for cursor opsition (meaning of H
).\[i;jH
means “move the cursor to row i
, column j
”If you think your function can be generaized, abstracted, irrelavant to the function name.
Refer to my quick note about function pointer.
fn progress<T>(v: Vec<T>, f: fn(&T)){
https://doc.rust-lang.org/rust-by-example/std/hash/hashset.html
Consider a
HashSet
as aHashMap
where we just care about the keys (HashSet<T>
is, in actuality, just a wrapper aroundHashMap<T, ()>
).“What’s the point of that?” you ask. “I could just store the keys in a
Vec
.”A
HashSet
’s unique feature is that it is guaranteed to not have duplicate elements. That’s the contract that any set collection fulfills.HashSet
is just one implementation. (see also:BTreeSet
)