Day 2:
You can find the text of the puzzle here.
Overview
The basic aim here is to take in a series of parcel dimensions (stored as strings of numbers separated by “x”) and use them to calculate the total amount of wrapping paper (part 1) and ribbon for bows (part 2).
Concepts
- using my own modules (including the
aoc_commoncode from SideQuest 1) - closures (getting totals)
- parsing Strings to
i32 - returning from
main(like I know what that means).
Limitations - Modules and Functions and tests (Oh My!)
As my aim isn’t to just solve the problems, but to work towards a polished solution and so I’m going to try to write as little code in main.rs as possible, and to start everything with tests (burgh).
Set up
# I'm working on NixOs
# move into my root
$ cd aoc
# create a new folder with cargo
$ cargo new day_2
// -- snip -- just output showing the project getting made
# create the parcel.rs file
$ touch day_2/src/parcel.rsSo that gives us most of what we need in the way of set up, but the last things I need to do are:
- add my aoc_common code as a dependency in
day_2/Cargo.toml1
[package]
name = "day_2"
version = "0.1.0"
edition = "2024"
[dependencies]
aoc_common = {path = "../aoc_common"} # this line- add
day_2to the workspace toml file
[workspace]
members = [
"aoc_common",
"day_2",
]Part 1
Formulae
This puzzle is about wrapping presents (which I’m calling parcels from here on out), and to get enough paper (in this economy?!) there are a few things we need to do:
Calculate its surface area
2 * (length * width) + 2 * (length * height) + 2 * (width * height)Calculate the area of the smallest face
- find the smallest side first
edge_1 * edge_2
Add the two products together
And finally we need to get the result of combining all those calculations.
Rolls up sleeves: Let’s kick this pig
Parcel Struct
Ok, so I’m going to try to put everything into a Parcel Struct inside parcel.rs and as per my TDD goal I’m going to start with a test.
test_new()
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
assert_eq!(
Parcel {
l : 1,
w : 2,
h : 3,
},
Parcel::new(1,2,3)
);
}
}And we have our first test. It just lets me test a new() associated method. It also contains the code to define the Struct, I just need to yank it up into a definition and set the field types.
Implement new()
// define the struct
#[derive(Debug, PartialEq)] //deriving needed traits
pub struct Parcel { // pub from the get go this time!
l : i32,
w : i32,
h : i32,
}
impl Parcel { // implement the `new()` function
pub fn new(l:i32, w: i32, h:i32) -> Parcel {
Parcel { l, w, h }
}
} Finished `test` profile [unoptimized + debuginfo] target(s) in 0.21s
Running unittests src/main.rs (/home/sp1d3r-z3r0/MyProjects-tmp/midwitsanonymous/scratch/target/debug/deps/day_2-f26cfbed71fb1d31)
running 1 test
test parcel::tests::test_new ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sThere it is; the first Boom! It’s not, like, earth-shaking because this isn’t exactly rocket surgery and I was pretty sure this would work. But, I’m not one to pass up a “boom” when I see one.
Makin’ calculations
We know from earlier that we have a few different calculations we need to perform to come up with our final answer. Rather than working my way in from the whole Parcel I’m going to work out from the area of the smallest side first.
test_smallest_area()
Let’s write the test
#[test]
fn test_smallest_area() {
// make a new parce
let test_parcel = Parcel::new(1,2,3);
assert_eq!(test_parcel.smallest_area(), 2
);
}And add it to our implementation
Perfectly fine Catastrophic smallest_area()
// -- snip --
pub fn smallest_area(&self) -> i32 {
let s1 = self.l * self.w;
let s2 = self.l * self.h;
let s3 = self.w * self.h;
let smallest = {
if s1 <= s2 & s1 <= s3 {
s1
} else if s2 <= s1 & s2 <= s3 {
s2
} else {
s3
}
};
smallest
}And there we have it, a perfectly fine implementation of the smallest_area() function. Yes indeedy!… Nothing wrong with this… probably could skip the test… but you know what, let’s just run it, for the look of the thing.
--> day_2/src/parcel.rs:19:19
|
19 | if s1 <= s2 & s1 <= s3 {
| ^^ ^^
|
help: split the comparison into two
|
19 | if s1 <= s2 & s1 && s2 & s1 <= s3 {
| ++++++++++
error: comparison operators cannot be chained
--> day_2/src/parcel.rs:21:26
|
21 | } else if s2 <= s1 & s2 <= s3 {
| ^^ ^^
|
help: split the comparison into two
|
21 | } else if s2 <= s1 & s2 && s1 & s2 <= s3 {
| ++++++++++
error[E0308]: mismatched types
--> day_2/src/parcel.rs:19:33
|
19 | if s1 <= s2 & s1 <= s3 {
| ------------- ^^ expected `bool`, found `i32`
| |
| expected because this is `bool`
error[E0308]: mismatched types
--> day_2/src/parcel.rs:21:40
|
21 | } else if s2 <= s1 & s2 <= s3 {
| ------------- ^^ expected `bool`, found `i32`
| |
| expected because this is `bool`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `day_2` (bin "day_2" test) due to 4 previous errorsWell… That’s fine

Oh, the hubris! Like Daedalus before me, certain of another Boom. Yet instead, I find myself the perpetrator of a (say it with me folks) Rust Rookie Mistake!
Again, Friend Computer is benevolent, showing me how to fix the error, but it’s worth taking a second for me to really mark that. To the documenatrium!!.
And and And
&
According to the docs the & symbol, as well as indicating a ‘borrow’ represents the “Bitwise AND” of two objects. Now I def’ine’itly ain’t the person to explain this but as I understand it 2, this performs the “AND” operations on the binary representation of 2 objects.
So let’s imagine that we have 2 sets of 4 bits
| column | 1 | 2 | 3 | 4 |
|---|---|---|---|---|
| row a | 0 | 0 | 1 | 1 |
| row b | 0 | 1 | 1 | 0 |
Reading down the columns we compare the 2 values:
- If they’re both 1s -> we get 1
- If they’re both 0s -> we get 0
- If they’re different values -> 0
So with the table above we’d get the value
| column | 1 | 2 | 3 | 4 |
|---|---|---|---|---|
| row a | 0 | 0 | 1 | 1 |
| row b | 0 | 1 | 1 | 0 |
| result | 0 | 0 | 1 | 0 |
Great, that’s a good thing for me to have learned… (I guess… kicks dust)
&&
As Friend Computer said up in our last Socratic dialogue, what I was really looking for was the “Short-circuiting logical AND” which allows us to combine multiple boolean conditions so that both must be true in order to move forward.
Which is what I naively thought I was doing above… in the before times. The Halcyon days if you will.
But why does this matter?3
Believe it or not, this is actually the first time I’ve encountered the coding equivalent of ‘PEMDAS’4. Again, I don’t know the ins-and-outs of it but essentially the ‘Bitwise’ & is higher in the hierarchy of operations than the comparison operators so where I thought I was writing
if x <= y and x <= z
I was actually writing
if x <= (y and x) <= z
Meaning the (x and y) happens first, which creates two further problems here (I think):
x <= (x and y)produces aboolwhilezis ani32: we can’t compare those types- unlike python, Rust doesn’t let us chain
comparison operatorslike that…
And all because I wanted to compare some numbers! Like a sweet summer child.
So… (cough) let’s do that
Fixing smallest_area()
// -- snip --
let smallest = {
if s1 <= s2 && s1 <= s3 {
s1
} else if s2 <= s1 && s2 <= s3 {
s2
} else {
s3
}
// -- snip --Breathe deeply and let’s test it
running 2 tests
test parcel::tests::test_new ... ok
test parcel::tests::test_smallest_area ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sAnd there’s the second BOOM!

The function calculates the areas of all three sides and then gives us back that value as an i32 that we can use in the next function.
wrapping_paper()
I want one function that can just hand us back the amount of wrapping paper needed. This means that it should use the smallest_area() function as part of the calculations on a given Parcel instance.
As always, our first step is to write a test for our wrapping_paper() function
test_wrapping()
// -- snip --
#[test]
fn test_wrapping_paper() {
// make a new parce
let test_parcel = Parcel::new(1,2,3);
assert_eq!(test_parcel.wrapping_paper(), 24
);
}
// -- snip --implementing wrapping_paper()
// -- snip --
pub fn wrapping_paper(&self) -> i32 {
{
2 * (self.l * self.w)
+ 2 * (self.l * self.h)
+ 2 * (self.w * self.h)
+ self.smallest_area()
}
}
// -- snip --In the past, in my youth of two function definitions ago, I would have faced this next cargo test confidently.
Pridefully.
But now, oh now, I just trust Friend Computer to give me guidance and to look upon my errors with grace.
running 3 tests
test parcel::tests::test_new ... ok
test parcel::tests::test_wrapping_paper ... ok
test parcel::tests::test_smallest_area ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sBOOM! I am a genius! I Always knew it! (cough, wheeze, push up glasses)
I mean… good. Boom.
So we have the functions to get the results we need for Part 1, and yet there’s something else I want to do before we pipe all this into main.
from_string()
Dear Reader, while the following passages are an accurate portrayal of Sisyphean struggle, this next section is long and dense with error output. It does show some truly top class “MidWittery” but if you want to just skip to the part where it gets solved… then go to How I was wrong.. But you’ll miss the stunning interpersonal drama!!
As we covered in SideQuest 1 I’m storing all the puzzle input in ./input.txt, and I wrote the aoc_common module to help reading it in. The input for this puzzle is in the format:
9x13x26
11x11x14
27x2x5
6x10x13
15x19x10
26x29x15
// -- snip -- Which will get pulled as:
"9x13x26\n
11x11x14\n
27x2x5\n
6x10x13\n
15x19x10\n
26x29x15\n
// -- snip --
8x23x6\n"Or one long string separated by newlines. from_string() is intended to take in an individual line of that text, trim the newline, split on the 'x' char and create a new Parcel from the 3 i32s.
Test first; ask questions later
Test from_string()
#[test]
fn from_string() {
// make a new parce
assert_eq!(
Parcel::new(1,2,3),
Parcel::from_string(String::from("1x2x3\n"))
);
}Then onto implementing the function
First attempt
pub fn from_string(dimensions: &str) -> Parcel {
// trim the \n
let numbers: Vec<i32> = dimensions
.trim_end()
.split("x")
.into_iter()
.map(|n| n.parse::<i32>())
.collect::<Vec<i32>>();
Parcel::new({numbers[0], numbers[1], numbers[2])
}Here’s the first shot at it.
We’re turning the input text (taking a &str because I believe it’s more flexible) and turning it into a vector of i32 numbers using the map iterator adaptor with a .parse() inside a closure.
Then, we use the . syntax to pass each of those numbers into our Parcel::new() associated function from earlier which gives us a Parcel we can return out of the function5.
Let’s see if this works?
error[E0277]: a value of type `Vec<i32>` cannot be built from an iterator over elements of type `Result<i32, ParseIntError>`
--> day_2/src/parcel.rs:23:43
|
23 | ... .collect::<Vec<i32>>();
| ------- ^^^^^^^^ value of type `Vec<i32>` cannot be built from `std::iter::Iterator<Item=Result<i32
, ParseIntError>>`
| |
| required by a bound introduced by this call
|
help: the trait `FromIterator<Result<i32, ParseIntError>>` is not implemented for `Vec<i32>`
but trait `FromIterator<i32>` is implemented for it
--> /rustc/59807616e1fa2540724bfbac14d7976d7e4a3860/library/alloc/src/vec/mod.rs:3862:0
= help: for that trait implementation, expected `i32`, found `Result<i32, ParseIntError>`
note: the method call chain might not have had the expected associated types
--> day_2/src/parcel.rs:22:33
|
18 | let numbers: Vec<i32> = dimensions
| ---------- this expression has type `&str`
19 | .trim_end()
20 | .split("x")
| ---------- `Iterator::Item` is `&str` here
21 | .into_iter()
| ----------- `Iterator::Item` remains `&str` here
22 | .map(|n| n.parse::<i32>())
| ^^^^^^^^^^^^^^^^^^^^^^^^^ `Iterator::Item` changed to `Result<i32, ParseIntError>` here
note: required by a bound in `collect`
--> /rustc/59807616e1fa254error[E0308]: mismatched types
--> day_2/src/parcel.rs:89:33
|
89 | Parcel::from_string(String::from("1x2x3\n"))
| ------------------- ^^^^^^^^^^^^^^^^^^^^^^^ expected `&str`, found `String`
| |
| arguments to this function are incorrect
|
note: associated function defined here
--> day_2/src/parcel.rs:16:12
|
16 | pub fn from_string(dimensions: &str) -> Parcel {
| ^^^^^^^^^^^ ----------------
help: consider borrowing here
|
89 | Parcel::from_string(&String::from("1x2x3\n"))
| +
Some errors have detailed explanations: E0277, E0308.0724bfbac14d7976d7e4a3860/library/core/src/iter/traits/iterator.rs:2051:4Bo–…
Ok, so jokes aside, this feels a really good learning error here. Let’s look at it from top to bottom and let me see if I understand what’s going on and what the fixes are telling me. There’s basically the same error here, just twice and in two different places.
- We can’t make a
Vec<i32>from themap(|n| n.parse::<i32>()) Parcel::from_string()is expecting a&strbut in the test I’ve passed it anownedString. (Flexible my ass…)
As always MidWit, you look but you do not observe!
map -> Result<T, E>
Believe it or not, this first error is a blessing, especially following on from SideQuest 1.
So what we’re being told here, which I did akshully know, was that map isn’t just giving me back i32s here. It’s giving me back the outcome of calling parse::<i32>() on each element in the collection that split("x") gives me.
Result<i32, ParseIntError>
You guys all saw this, but this is our second RRM6 of the puzzle.
If I understand this right: the parse method can fail, like a lot of things in rust, and so when we use map to call that on each object in a collection, we can’t be certain that all of the objects are amenable to being parsed. Like, what if the string was “1x2xbannanana\n”? I’d look like a fool then wouldn’t I?
So map gives us back Results that we need to handle just as we would anywhere else. This means that I could use unwrap() or expect() to make the function panic and stop the program if one of the given values wasn’t… parcable?… parcible?… numeric. However, given that the aim here is to reach to what I believe are good practices, maybe this gives us access to the ? operator, and to returning a Result<Parcel, ParseIntError>?
Nothin’ to it but to do it.
Second attempt: trying ?
use core::{iter::IntoIterator, num::ParseIntError}; // nvim/my lsp added these for me
// -- snip --
pub fn from_string(dimensions: &str) -> Result<Parcel, ParseIntError> {
// trim the \n
let numbers: Vec<i32> = dimensions
.trim_end()
.split("x")
.into_iter()
.map(|n| n.parse::<i32>())
.collect::<Vec<Ok(i32)>>()?; //changed the return type and added?
Ok(Parcel::new(numbers[0], numbers[1], numbers[2])) // returing an Ok(Parcel) now
}
// -- snip --
// updating the test
#[test]
fn from_string() {
// make a new parce
assert_eq!(
Parcel::new(1,2,3),
Parcel::from_string("1x2x3\n").unwrap()
);
}Lot of changes up there, but in brief:
- My editor gave me some
usestatements to bring some things that I’d need into scope. Thanks Obama. - I’ve changed the return types in a few places to put
Resultor itsvariantseverywhere. - I’ve updated the test to reflect this (I was clearly wrong about the flexibility of slices).
Now, I’m 90% sure that I’ve got the wrong turbofish on the collect() and that the ? operator is in the wrong place, but we’re here to learn so let’s FISI and see what we get.
Dear Reader, he did in fact have the wrong turbofish.
He did not, in fact, have the ? in the wrong place.
error[E0573]: expected type, found variant `Ok`
--> day_2/src/parcel.rs:23:47
|
23 | ... .collect::<Vec<Ok(i32)>>()?;
| ^^^^^^^ not a type
|
help: try using the variant's enum
|
23 - .collect::<Vec<Ok(i32)>>()?;
23 + .collect::<Vec<core::result::Result>>()?;
|
23 - .collect::<Vec<Ok(i32)>>()?;
23 + .collect::<Vec<std::result::Result>>()?;
|
For more information about this error, try `rustc --explain E0573`.
error: could not compile `day_2` (bin "day_2" test) due to 1 previous errorOk, I was Schrodinger’s correct again!
No, in fact the cat was just dead.
The turbofish needs the Result and not the variant. However the ? doesn’t seem to be throwing me an error… so The MidWit Strikes again!!
// -- snip --
pub fn from_string(dimensions: &str) -> Result<Parcel, ParseIntError> {
// trim the \n
let numbers: Vec<i32> = dimensions
.trim_end()
.split("x")
.into_iter()
.map(|n| n.parse::<i32>())
.collect::<Vec<Result<i32, ParseIntError>>>()?;
Ok(Parcel::new(numbers[0], numbers[1], numbers[2]))
}error[E0277]: the `?` operator can only be applied to values that implement `Try`
--> day_2/src/parcel.rs:18:32
|
18 | let numbers: Vec<i32> = dimensions
| ________________________________^
19 | | .trim_end()
20 | | .split("x")
21 | | .into_iter()
22 | | .map(|n| n.parse::<i32>())
23 | | .collect::<Vec<Result<i32, ParseIntError>>>()?;
| |_____________________________________________________________________________^ the `?` operator cannot be applied to type `
Vec<Result<i32, ParseIntError>>`
|
= help: the nightly-only, unstable trait `Try` is not implemented for `Vec<Result<i32, ParseIntError>>`There it is!! Turns out I was entirely right; I did get it completely wrong!! The ? is also wrong. I knew it! I am a genius!
He’s now multiple steps deep in the wrong.
Honestly, my hope here is that this is telling me that the ? needs to either go on the parse or on the map. Imma try both, and then… I’ll ask Claude if this is even possible.
I’m not going to put two more big blocks of code in there just to show that I’ve moved one symbol around. So map(..)? first.
// --snip --
error[E0277]: the `?` operator can only be applied to values that implement `Try`
--> day_2/src/parcel.rs:18:32
|
18 | let numbers: Vec<i32> = dimensions
| ________________________________^
19 | | .trim_end()
20 | | .split("x")
21 | | .into_iter()
22 | | .map(|n| n.parse::<i32>())?
| |__________________________________________________________^ the `?` operator cannot be applied to type `Map<std::str::Split
<'_, &str>, {closure@day_2/src/parcel.rs:22:37: 22:40}>`
// -- snip --Same error, so map(|n| n.parse::<i32>()?) next.
error[E0277]: the `?` operator can only be used in a closure that returns `Result` or `Option` (or another type that implements `
FromResidual`)
--> day_2/src/parcel.rs:22:57
|
22 | ... .map(|n| n.parse::<i32>()?)
| --- ^ cannot use the `?` operator in a closure that returns `i32`
| |
| this function should return `Result` or `Option` to accept `?`
error[E0277]: a value of type `Vec<Result<i32, ParseIntError>>` cannot be built from an iterator over elements of type `i32`
--> day_2/src/parcel.rs:23:43
|
23 | ... .collect::<Vec<Result<i32, ParseIntError>>>();
| ------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ value of type `Vec<Result<i32, ParseIntError>>` cannot be b
uilt from `std::iter::Iterator<Item=i32>`
| |
| required by a bound introduced by this call
|
help: the trait `FromIterator<i32>` is not implemented for `Vec<Result<i32, ParseIntError>>`
but trait `FromIterator<Result<i32, ParseIntError>>` is implemented for it
--> /rustc/59807616e1fa2540724bfbac14d7976d7e4a3860/library/alloc/src/vec/mod.rs:3862:0
= help: for that trait implementation, expected `Result<i32, ParseIntError>`, found `i32`
note: the method call chain might not have had the expected associated types
--> day_2/src/parcel.rs:22:33
|
18 | let numbers: Vec<i32> = dimensions
| ---------- this expression has type `&str`
19 | .trim_end()
20 | .split("x")
| ---------- `Iterator::Item` is `&str` here
21 | .into_iter()
| ----------- `Iterator::Item` remains `&str` here
22 | .map(|n| n.parse::<i32>()?)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ `Iterator::Item` changed to `i32` here
note: required by a bound in `collect`
--> /rustc/59807616e1fa2540724bfbac14d7976d7e4a3860/library/core/src/iter/traits/iterator.rs:2051:4
error[E0308]: mismatched types
--> day_2/src/parcel.rs:18:32
|
18 | let numbers: Vec<i32> = dimensions
| _____________________--------___^
| | |
| | expected due to this
19 | | .trim_end()
20 | | .split("x")
21 | | .into_iter()
22 | | .map(|n| n.parse::<i32>()?)
23 | | .collect::<Vec<Result<i32, ParseIntError>>>();
| |____________________________________________________________________________^ expected `Vec<i32>`, found `Vec<Result<i32, ParseIntError>>`
|
= note: expected struct `Vec<i32>`
found struct `Vec<Result<i32, ParseIntError>>`_ Huh… That’s… a lot…
What we got here… Is a failure… to communicate.
That move changed a lot more than I expected. Either I missed something in earlier output, or adding the ? operator directly to the parse is handling the Result for me…
He did, in fact, miss something earlier.
Ok, I know I said I’d ask Claude if I couldn’t figure it out, but just gimme a sec here.
// -- snip --
pub fn from_string(dimensions: &str) -> Result<Parcel, ParseIntError> {
// trim the \n
let numbers: Vec<i32> = dimensions
.trim_end()
.split("x")
.into_iter()
.map(|n| n.parse::<i32>()?)
.collect::<Vec<i32>>(); // note that we've gone back to Vec<i32> here
Ok(Parcel::new(numbers[0], numbers[1], numbers[2]))
}error[E0277]: the `?` operator can only be used in a closure that returns `Result` or `Option` (or another type that implements `
FromResidual`)
--> day_2/src/parcel.rs:22:57
|
22 | ... .map(|n| n.parse::<i32>()?)
| --- ^ cannot use the `?` operator in a closure that returns `i32`
| |
| this function should return `Result` or `Option` to accept `?`
For more information about this error, try `rustc --explain E0277`.Well… crap.
I thought I was onto something there. But as they say, faint hearts never won fair maiden so I’m going to try something a bit less ergonomic but that might help this click for me.
Getting Explicit: Dropping ?
// -- snip --
pub fn from_string(dimensions: &str) -> Result<Parcel, ParseIntError> {
// trim the \n
let numbers: Vec<i32> = dimensions
.trim_end()
.split("x")
.into_iter()
.map(|n| {
let num = n.parse::<i32>();
let d = match num {
Ok(i) => i,
Err(_e) => {
println!("You didn't get a number fool");
0
}
};
d
}
)
.collect::<Vec<i32>>();
Ok(Parcel::new(numbers[0], numbers[1], numbers[2]))
}
// -- snip --That’s… ugly code… and I’m pretty sure that the return type is wrong
running 4 tests
test parcel::tests::from_string ... ok
test parcel::tests::test_new ... ok
test parcel::tests::test_smallest_area ... ok
test parcel::tests::test_wrapping_paper ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00But, explicitly handling the error lets the test pass… great.
Right! Time to stop stumbling around.
Hang on, Dear Reader. This is nearly over, and there’s a shocking betrayal coming up!!
What do I think I know?
You definitely already know where I’m going wrong, but I’m not seeing it.
Based on how I’m understanding the various error messages, the main problem here is my misunderstanding what types are getting produced at each step, and therefore where ? might be relevant. Friend Computer gives good feedback, but it’s ephemeral, it doesn’t know what has been tried previously or what misconceptions I have.
If I’m trying to learn-by-doing here it would probably be a good idea to reset and try to think through where I’m at.
So what am I assuming here?
splitis giving me back aSplitobject which is a collection in itself (caveats aside).maptakes that collection, lets me run some function over it giving me back a collection (after.collect()).mapwill also give me an error if it encounters something the internal function can’t work on.
parse()gives me aResult<i32, ParseIntError>collect()gives me back the collection.- the
?operator can only be called on something that produces aResultorOption…
Which reminds me of something else.
- calling next on a collection can return an
OptionwithSome(T)when there is a thing at that position in the collection. - However, if there’s nothing at that position, say if we’ve reached the end of the array, it can give us
None.
So, maybe instead of Vec<Result<i32, ParseIntError>> in the .collect() turbofish (which just seemed wrong anyway) maybe it should be a Vec<Option<i32>>.
Trying Option
pub fn from_string(dimensions: &str) -> Result<Parcel, ParseIntError> {
// trim the \n
let numbers: Vec<i32> = dimensions
.trim_end()
.split("x")
.into_iter()
.map(|n| n.parse::<i32>()?)
.collect::<Vec<Option<i32>>>();
Ok(Parcel::new(numbers[0], numbers[1], numbers[2]))
}error[E0277]: the `?` operator can only be used in a closure that returns `Result` or `Option` (or another type that implements `
FromResidual`)
--> day_2/src/parcel.rs:22:57
|
22 | ... .map(|n| n.parse::<i32>()?)
| --- ^ cannot use the `?` operator in a closure that returns `i32`
| |
| this function should return `Result` or `Option` to accept `?`
error[E0277]: a value of type `Vec<Option<i32>>` cannot be built from an iterator over elements of type `i32`
--> day_2/src/parcel.rs:23:43
|
23 | ... .collect::<Vec<Option<i32>>>();
| ------- ^^^^^^^^^^^^^^^^ value of type `Vec<Option<i32>>` cannot be built from `std::iter::Iterator
<Item=i32>`
| |
| required by a bound introduced by this call
|
help: the trait `FromIterator<i32>` is not implemented for `Vec<Option<i32>>`
but trait `FromIterator<Option<i32>>` is implemented for it
--> /rustc/59807616e1fa2540724bfbac14d7976d7e4a3860/library/alloc/src/vec/mod.rs:3862:0
= help: for that trait implementation, expected `Option<i32>`, found `i32`
note: the method call chain might not have had the expected associated types
--> day_2/src/parcel.rs:22:33
|
18 | let numbers: Vec<i32> = dimensions
| ---------- this expression has type `&str`
19 | .trim_end()
20 | .split("x")
| ---------- `Iterator::Item` is `&str` here
21 | .into_iter()
| ----------- `Iterator::Item` remains `&str` here
22 | .map(|n| n.parse::<i32>()?)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ `Iterator::Item` changed to `i32` here
note: required by a bound in `collect`
--> /rustc/59807616e1fa2540724bfbac14d7976d7e4a3860/library/core/src/iter/traits/iterator.rs:2051:4
error[E0308]: mismatched types
--> day_2/src/parcel.rs:18:32
|
18 | let numbers: Vec<i32> = dimensions
| _____________________--------___^
| | |
| | expected due to this
19 | | .trim_end()
20 | | .split("x")
21 | | .into_iter()
22 | | .map(|n| n.parse::<i32>()?)
23 | | .collect::<Vec<Option<i32>>>();
| |_____________________________________________________________^ expected `Vec<i32>`, found `Vec<Option<i32>>`
|
= note: expected struct Vec<i32>Sigh…
Getting “help”
Ok, so… that’s very similar to an earlier error and we both know that this ain’t an interesting read…
See? He’s not oblivious.
He keeps missing that he’s typehinting result as Vec<i32>, but he’s not completely oblivious.
Here comes the betrayal. Don’t look away, but hold your loved ones close.
I have a passing solution, I just don’t like it. Rather than staying nerdsniped though, I’m going to ask Claude if the idea is actually possible. I’m going to pass what I’ve written in with the prompt along with the question
My question
Here’s what I’ve written so far, and ,I’ve also tried just dropping the
?entirly and turbofishing both avec<i32>and aVec<Result<i32, ParseIntError>>and both failed. So I’m not understanding something about this order. The compiler tells me that the map is returning a result<i32, _> and that indicates to me that I should be able to write this more ergonomically than I have in the passing version, maybe that’s an if let rather than?Just tell me, with no editorialising or additional guidance, can I use?in this context?
Claude’s answer
Yes — but not the way you’ve been placing it, and not on the closure inside
map. The?goes on thecollect, not inside themap. And the single thing that makes it work, which you haven’t tried, is theturbofish: you have to collect intoResult<Vec<i32>, ParseIntError>— theResulton the outside,Vecinside — notVec<Result<...>>. Once the collect produces a singleResult,?has something it can be applied to. Your two collect attempts both failed becauseVec<i32>can’t be built from Result items, andVec<Result<...>>is a vector, which?can’t touch. The shape you never tried is the one that works.
Welp! That sucks.
Et tu Brute?!
You can’t see this Dear Reader, but he’s livid.
I’m going to… set aside the fact that Claude just robbed me of figuring this out on my own by completely ignoring
“Just tell me, with no editorialising or additional guidance[…]”
and handing me the answer7. But to be clear I’m really annoyed.
The raw human suffering is hard to read.
How I was wrong.
Anyway, I think I see what’s happening here that I wasn’t getting. As I said map() lets me apply a function to a set of things, and either gives me back the collection of things (when I call .collect()). What I wasn’t getting is that it doesn’t give me the new collection and an error… It gives me a Result<NewCollection, Error>.
So in this case, collect() was the correct place for the ?. My turbofish and type hinting in general was the problem. I had the hierarchy of the Vec and the Result flipped because of the nature of the operation. It’s safe to say that I was typehinting wrong everywhere and not seeing the whole “board”. Good to watch in future.
sniff… poor kid
I’d like to think that I would have got there if I kept thinking it through, but, now the opportunity is lost to me. Couple of lessons learned there I guess.
from_string() solution
Alright, to continue twirling t’wards freedom, let’s get the from_string() functions wired up and test it.
So ruddy, bloody brave!
// -- snip --
pub fn from_string(dimensions: &str) -> Result<Parcel, ParseIntError> {
// trim the \n
let numbers: Vec<i32> = dimensions
.trim_end()
.split("x")
.into_iter()
.map(|n| n.parse::<i32>())
.collect::<Result<Vec<i32>, ParseIntError>>()?;
Ok(Parcel::new(numbers[0], numbers[1], numbers[2]))
}
// -- snip --running 4 tests
test parcel::tests::from_string ... ok
test parcel::tests::test_new ... ok
test parcel::tests::test_wrapping_paper ... ok
test parcel::tests::test_smallest_area ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sNo… No Boom for this. Let’s just get out of here with the shirt on our backs shall we?
Bringing it all into main.rs
The first thing to do to bring our code into main.rs is to declare the module and then add the use statement(s). I’m going to bring both the Parcel module and the aoc_common crate that I’ve previously defined
mod parcel;
use aoc_common::read_challenge_input;
use parcel::Parcel;
// -- snip --There. The reinforcements that we called up earlier are now ready, so we should be able to write this solution up neatly.
// -- snip --
fn main() {
let dimensions = read_challenge_input("./input.txt"); // input is in the root folder which is
// where I'll be calling the cargo run
// from
let wraps: i32 = dimensions.unwrap().lines()
.into_iter()
.map(|l| Parcel::from_string(l))
.map(|p| p.expect("This should be a set of dimensions").wrapping_paper())
.sum();
println!("{}", wraps);
}3 lines in main and I have a result
:!cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
Running `/home/midwitsanonymous/aoc/target/debug/day_2`
1586300I promise that’s correct for my puzzle dimensions. And while it’s short and it passes, after my disastrous interaction with Claude earlier, my ego needs to produce something a little… goofier.
// -- snip --
fn main() -> Result<(), Box<dyn Error>> {
// This is stupid
println!("Hey Larry! Order {:?} feet of paper!",{
read_challenge_input("./input.txt")?
.lines()
.into_iter()
.map(|l| Parcel::from_string(l))
.map(|p| p.expect("This should be a set of dimensions").wrapping_paper())
.sum::<i32>();
});
Ok(())
}Guh’hyuk!! Gorsh that’s silly!
What we’ve done here is make the main function return a Result<(), Box<dyn Error>>. This lets us propagate any error we get inside main back out to the console. This requires that the last line within main returns a unit Struct wrapped in Ok(). Then, and I take full responsibility for this rediculosity, I put everything inside an expression inside the println! macro.
Let’s see what we get when passing a bad path into the read_challenge_input() call.
$!cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
Running `/home/scratch/target/debug/day_2`
Error: FileNotFound("No such file or directory (os error 2)")One of our lovely FileNotFound errors from our previous SideQuest.
Ok, fair enough, let us leave these Hollowed out lands of Part 1, and seek new Booms in the fairer shores of
(Dun-dun-na-na!)
Part 2
Here, we need to calculate the length of ribbon. Though, this may also work for getting bandages (you know, for all the scars… you get me).
To get the length of ribbon that we’ll need for our bows we need:
- The perimeter of the smallest side
- The cubic volume of the
Parcel
You know the score
smallest_perimeter()
First we write the test
test smallest_perimeter()
// -- snip --
#[test]
fn test_perimeter() {
let test = Parcel::new(1,2,3);
assert_eq!(
test.smallest_perimeter(),
6
)
}implementing smallest_perimeter()
The battle scars from our previous foray serve us well here. I’m basically just reusing the same solutions from smallest_area() again.
// -- snip --
pub fn smallest_perimeter(&self) -> i32 {
let p1 = (self.l * 2) + (self.w * 2);
let p2 = (self.l * 2) + (self.h * 2);
let p3 = (self.w * 2) + (self.h * 2);
if p1 <= p2 && p1 <= p3 {
p1
} else if p2 <= p1 && p2 <= p3 {
p2
} else {
p3
}
}running 5 tests
test parcel::tests::test_smallest_area ... ok
test parcel::tests::test_new ... ok
test parcel::tests::test_perimeter ... ok
test parcel::tests::from_string ... ok
test parcel::tests::test_wrapping_paper ... ok
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sBoom.
Let’s take the win and keep truckin’
cubic_volume() test
// -- snip --
#[test]
fn test_cubic_volume() {
let test = Parcel::new(1,2,3);
assert_eq!(
test.cubic_volume(),
6
)
}implementing cubic_volume()
pub fn cubic_volume(&self) -> i32 {
self.l * self.w * self.h
}Isn’t that a cute little function? Lookit’im!
So let’s see how cargo feels about it
// -- snip --
running 6 tests
test parcel::tests::from_string ... ok
test parcel::tests::test_new ... ok
test parcel::tests::test_cubic_volume ... ok
test parcel::tests::test_perimeter ... ok
test parcel::tests::test_smallest_area ... ok
test parcel::tests::test_wrapping_paper ... ok
test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sCubic Boom!
ribbon()
So now, all we need to do is take both of those functions and put them into a single function that gives us what we want.
ribbon() test
// -- snip --
#[test]
fn test_ribbon() {
let test = Parcel::new(1,2,3);
assert_eq!(
test.ribbon(),
12
)
}And the implementation
// -- snip --
pub fn ribbon(&self) -> i32 {
self.smallest_perimeter() + self.cubic_volume()
}Let’s get Friend computer's thoughts on it all
// -- snip --
running 7 tests
test parcel::tests::from_string ... ok
test parcel::tests::test_new ... ok
test parcel::tests::test_cubic_volume ... ok
test parcel::tests::test_perimeter ... ok
test parcel::tests::test_ribbon ... ok
test parcel::tests::test_smallest_area ... ok
test parcel::tests::test_wrapping_paper ... ok
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sThere we have it, the penultimate Boom.
Part 2 in main
OK, so to get our final solution we need to put all this together in main.rs. Which in this case is really just a case of copying what’s already in main() and redoing it.
Complete main.rs
mod parcel;
use aoc_common::read_challenge_input;
use parcel::Parcel;
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
let dimensions = read_challenge_input("./input.txt")?;
let paper = dimensions
.lines()
.into_iter()
.map(|l| Parcel::from_string(l))
.map(|p| p.expect("This should be a set of dimensions").wrapping_paper())
.sum::<i32>();
let ribbon = dimensions
.lines()
.into_iter()
.map(|l| Parcel::from_string(l))
.map(|p| p.expect("This should be a set of dimensions").ribbon())
.sum::<i32>();
println!("Hey Larry! Order {} feet of paper, and {} of ribbon!", paper, ribbon);
Ok(())
}As we’re coming to a close, it felt right to let the goofy implementation from earlier go. To move on bright eyed and hopeful into a more mature place.
Nature’s healing.
Let’s run it and wrap this sucker up.
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
Running `/home//target/debug/day_2`
Hey Larry! Order 1586300 feet of paper, and 3742592 of ribbon!What’s that? Off in the distance…
Boom (oom, oom)
sniff So proud
What do I need to take from this?
This one was a doozy, and if you read this far (thank you), you can probably spot the moment when the joy of solving this puzzle faded.
However, the positive note is that both the stumbling around, and the fumble with Claude, have reinforced the things I needed to learn, so hopefully those Rust Rookie Mistakes are now things of the past.
Part of the power of LLM’s is that they can really speed up the production of code, but when that’s not the goal, when the goal is the thinking and the making mistakes and massive dopamine hit of getting the code to run… well then that power is a curse.
I often find docs tough to read, especially in a new language, and especially when those docs are written to help experts, not necessarily to help new comers. I’ve found that asking for toy examples, or even just for guidance on idioms has been useful. Every now and then though, something like what happened earlier on happens and it seems like that isn’t a current limitation of the technology. It’s the nature of the beast.
When we make a stochastic puppet who’s job is to make itself easier to use and pleasant to talk to in most circumstances then using it to learn is a real risk. Not even including the risks of plagiarism, or of appealing to the lazy angels of our nature (and Lord knows mine are already strong enough), or of getting us to confuse familiarity with knowledge. There’s also the risk that the biases built in will make it default to doing “work” for us. Even when the work is what we want.
I don’t know how to manage this as a human, but today is another piece of data to help I guess.
Enough philosophising!
The sticking point of not seeing the difference between
Vec<Result<i32, ParseIntError>Result<Vec<i32>, ParseIntError>
Feels like the kind of thing that you just have to see. That comes from getting reps in. That’s a good thing to keep in mind in the future.
Also thinking about closures holistically. Seeing the ‘end in the beginning’ so to speak. .collect(), or any of the other consumers will be in a specific relationship with the adaptors that came before it and it’d be good to get more reps in on that.
The & vs && is a good to know, and I’ll probably make that mistake every time I return the Rust after a short break.
TDD served me well here again, but it really only really mattered when I was thinking through from_string(), and even then, not enough to actually help solve the central problem. Just to tell me when I had a solution.
Coming from the python data-sciency space, TDD actually feels a little more like REPL-driven development, but just for stuff that doesn’t have an immediate output. If I make a polars LF in a Marimo notebook cell, I can see really easily if I have what I want. I don’t need to wire up a lot of API calls, or fixtures to see what I need. Maybe TDD is just another way of implementing the same kind of feedback loop? Maybe that’s where it’s useful.
Think about what you want > write the test > get feedback.
Again, that’s probably not shocking to people who practice TDD, but coming from my viewpoint it feels like a good model.
REPLs are really nice to have though.
Anyway, that’s more than enough for this one. Again, if you made it through with me, thank you for your time.
God’s speed.
The MidWit :wqa
Footnotes
Neovim by the way↩︎
And remember “MidWit”.↩︎
I asked Claude for help understanding this, the words are mine, but like I said, this was new to me↩︎
Also called BOMDAS in some places but I learned PEMDAS so that’s what I’m going with.↩︎
It’s hard to describe a function humorously, but maybe the test output will give us a giggle huh?↩︎
TM, TM, TM↩︎
I’m doing these puzzles to learn by solving problems. The feeling of figuring something out is the best feeling in the world. I had hoped that using Claude here might just help me not go down completely dead ends, instead, it robbed me of the game. If your response to this is that I’m using it wrong: Go back to bed please.↩︎