blog(aoc-2022): add post mortems to the first two weeks

Signed-off-by: Matej Focko <me@mfocko.xyz>
This commit is contained in:
Matej Focko 2023-01-01 22:38:35 +01:00
parent 5db41d357f
commit 5e93a6bf94
Signed by: mfocko
GPG key ID: 7C47D46246790496
2 changed files with 239 additions and 0 deletions

View file

@ -468,6 +468,33 @@ the same time as parsing. That could be done by adding additional fields to the
nodes which would allow storing such information and updating it as we construct
the filesystem.
## Post Mortem
Things that have been brought up in the discussion later on.
### `Rc<T>` vs `Rc<RefCell<T>>`
It has been brought up that I have a contradicting statement regarding the
dynamically allocated memory. Specifically:
- You can imagine `Rc<T>` as an `std::shared_ptr<T>` (in C++)
- When you want an equivalent of `std::shared_ptr<T>`, you want to use
`Rc<RefCell<T>>`
Now, in Rust it is a bit more complicated, because the type that represents the
„shared pointer“ is `Rc<T>`. What `RefCell<T>` does is making sure that there is
only one „owner“ of a mutable reference at a time (and dynamically, as opposed
to the `Cell<T>`).
Therefore to be precise and correct about the equivalents of `std::shared_ptr<T>`
in Rust, we can say that
- `Rc<T>` is an equivalent of a `const std::shared_ptr<T>`,
- and `Rc<RefCell<T>>` is an equivalent of a `std::shared_ptr<T>`.
You can easily see that they only differ in the mutability. (And even that is not
as simple as it seems, because there is also `Cell<T>`)
[_Advent of Code_]: https://adventofcode.com
[GitLab]: https://gitlab.com/mfocko/advent-of-code-2022
[`/src/bin/`]: https://gitlab.com/mfocko/advent-of-code-2022/-/tree/main/src/bin

View file

@ -657,5 +657,217 @@ it up just a bit.
Tedious.
## Post Mortem
### Indexing
I was asked about the indexing after publishing the blog. And truly it is rather
complicated topic, especially after releasing `SliceIndex<I>` trait. I couldn't
leave it be, so I tried to implement the `Index` and `IndexMut` trait.
:::note
I have also mentioned that the `SliceIndex` trait is `unsafe`, but truth be told,
only _unsafe_ part are the 2 methods that are named `*unchecked*`. Anyways, I will
be implementing the `Index*` traits for now, rather than the `SliceIndex`.
:::
It's relatively straightforward…
```rust
impl<I, C> Index<Vector2D<I>> for [C]
where
I: Copy + TryInto<usize>,
<I as TryInto<usize>>::Error: Debug,
C: Index<usize>,
{
type Output = C::Output;
fn index(&self, index: Vector2D<I>) -> &Self::Output {
let (x, y): (usize, usize) =
(index.x.try_into().unwrap(), index.y.try_into().unwrap());
&self[y][x]
}
}
impl<I, C> IndexMut<Vector2D<I>> for [C]
where
I: Copy + TryInto<usize>,
<I as TryInto<usize>>::Error: Debug,
C: IndexMut<usize>,
{
fn index_mut(&mut self, index: Vector2D<I>) -> &mut Self::Output {
let (x, y): (usize, usize) =
(index.x.try_into().unwrap(), index.y.try_into().unwrap());
&mut self[y][x]
}
}
```
We can see a lot of similarities to the implementation of `index` and `index_mut`
functions. In the end, they are 1:1, just wrapped in the trait that provides a
syntax sugar for `container[idx]`.
:::note
I have also switched from using the `TryFrom` to `TryInto` trait, since it better
matches what we are using, the `.try_into` rather than `usize::try_from`.
Also implementing `TryFrom` automatically provides you with a `TryInto` trait,
since it is relatively easy to implement. Just compare the following:
```rust
pub trait TryFrom<T>: Sized {
type Error;
fn try_from(value: T) -> Result<Self, Self::Error>;
}
pub trait TryInto<T>: Sized {
type Error;
fn try_into(self) -> Result<T, Self::Error>;
}
```
:::
OK, so we have our trait implemented, we should be able to use `container[index]`,
right? Yes… but actually no :frowning:
```
error[E0277]: the type `[std::vec::Vec<i8>]` cannot be indexed by `aoc_2022::Vector2D<usize>`
--> src/bin/day08.rs:26:18
|
26 | if trees[pos] > tallest {
| ^^^ slice indices are of type `usize` or ranges of `usize`
|
= help: the trait `std::slice::SliceIndex<[std::vec::Vec<i8>]>` is not implemented for `aoc_2022::Vector2D<usize>`
= note: required for `std::vec::Vec<std::vec::Vec<i8>>` to implement `std::ops::Index<aoc_2022::Vector2D<usize>>`
error[E0277]: the type `[std::vec::Vec<i8>]` cannot be indexed by `aoc_2022::Vector2D<usize>`
--> src/bin/day08.rs:30:28
|
30 | max(tallest, trees[pos])
| ^^^ slice indices are of type `usize` or ranges of `usize`
|
= help: the trait `std::slice::SliceIndex<[std::vec::Vec<i8>]>` is not implemented for `aoc_2022::Vector2D<usize>`
= note: required for `std::vec::Vec<std::vec::Vec<i8>>` to implement `std::ops::Index<aoc_2022::Vector2D<usize>>`
error[E0277]: the type `[std::vec::Vec<i8>]` cannot be indexed by `aoc_2022::Vector2D<isize>`
--> src/bin/day08.rs:52:28
|
52 | let max_height = trees[position];
| ^^^^^^^^ slice indices are of type `usize` or ranges of `usize`
|
= help: the trait `std::slice::SliceIndex<[std::vec::Vec<i8>]>` is not implemented for `aoc_2022::Vector2D<isize>`
= note: required for `std::vec::Vec<std::vec::Vec<i8>>` to implement `std::ops::Index<aoc_2022::Vector2D<isize>>`
```
Why? We have it implemented for the slices (`[C]`), why doesn't it work? Well,
the fun part consists of the fact that in other place, where we were using it,
we were passing the `&[Vec<T>]`, but this is coming from a helper functions that
take `&Vec<Vec<T>>` instead. And… we don't implement `Index` and `IndexMut` for
those. Just for the slices. :exploding_head: *What are we going to do about it?*
We can either start copy-pasting or be smarter about it… I choose to be smarter,
so let's implement a macro! The only difference across the implementations are
the types of the outer containers. Implementation doesn't differ **at all**!
Implementing the macro can be done in a following way:
```rust
macro_rules! generate_indices {
($container:ty) => {
impl<I, C> Index<Vector2D<I>> for $container
where
I: Copy + TryInto<usize>,
<I as TryInto<usize>>::Error: Debug,
C: Index<usize>,
{
type Output = C::Output;
fn index(&self, index: Vector2D<I>) -> &Self::Output {
let (x, y): (usize, usize) =
(index.x.try_into().unwrap(), index.y.try_into().unwrap());
&self[y][x]
}
}
impl<I, C> IndexMut<Vector2D<I>> for $container
where
I: Copy + TryInto<usize>,
<I as TryInto<usize>>::Error: Debug,
C: IndexMut<usize>,
{
fn index_mut(&mut self, index: Vector2D<I>) -> &mut Self::Output {
let (x, y): (usize, usize) =
(index.x.try_into().unwrap(), index.y.try_into().unwrap());
&mut self[y][x]
}
}
};
}
```
And now we can simply do
```rust
generate_indices!(VecDeque<C>);
generate_indices!([C]);
generate_indices!(Vec<C>);
// generate_indices!([C; N], const N: usize);
```
The last type (I took the inspiration from the implementations of the `Index` and
`IndexMut` traits) is a bit problematic, because of the `const N: usize` part,
which I haven't managed to be able to parse. And that's how I got rid of the error.
:::note
If I were to use 2D-indexing over `[C; N]` slices, I'd probably just go with the
copy-paste, cause the cost of this „monstrosity“ outweighs the benefits of no DRY.
:::
#### Cause of the problem
This issue is relatively funny. If you don't use any type aliases, just the raw
types, you'll get suggested certain changes by the _clippy_. For example if you
consider the following piece of code
```rust
fn get_sum(nums: &Vec<i32>) -> i32 {
nums.iter().sum()
}
fn main() {
let nums = vec![1, 2, 3];
println!("Sum: {}", get_sum(&nums));
}
```
and you run _clippy_ on it, you will get
```
Checking playground v0.0.1 (/playground)
warning: writing `&Vec` instead of `&[_]` involves a new object where a slice will do
--> src/main.rs:1:18
|
1 | fn get_sum(nums: &Vec<i32>) -> i32 {
| ^^^^^^^^^ help: change this to: `&[i32]`
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#ptr_arg
= note: `#[warn(clippy::ptr_arg)]` on by default
warning: `playground` (bin "playground") generated 1 warning
Finished dev [unoptimized + debuginfo] target(s) in 0.61s
```
However, if you introduce a type alias, such as
```rust
type Numbers = Vec<i32>;
```
Then _clippy_ won't say anything, cause there is literally nothing to suggest.
However the outcome is not the same…
[_Advent of Code_]: https://adventofcode.com
[BFS above]: #day-12-hill-climbing-algorithm