mirror of
https://github.com/mfocko/blog.git
synced 2024-11-25 14:21:55 +01:00
blog(aoc-2022): add post mortems to the first two weeks
Signed-off-by: Matej Focko <me@mfocko.xyz>
This commit is contained in:
parent
5db41d357f
commit
5e93a6bf94
2 changed files with 239 additions and 0 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue