Nibbles of Rust – Restructuring Patterns(catmonad.xyz)
catmonad.xyz
Nibbles of Rust – Restructuring Patterns
https://www.catmonad.xyz/blog/nibbles_02.html
30 comments
Since the author mentions at the end that they were motivated to write and publish this as a part of a writing group, I'd like to say that this was a very pleasant read. Tightly-scoped but insightful and informative!
Thanks! (Post author. I made an HN account to reply here, haha.)
Nice tip.
I honestly am a bit frustrated with Rusts pattern matching ergonomics, cuz it ends up requiring me to introduce indentation in places where I don’t want it, mainly to futz around with unwrapping structures or the like.
Lots of helper methods exist on standard library enumerations but I really want there to be some nice built in macros to just give me the innards when I need it, and have a control flow block for when I _cant_ pattern match (and thus want to do an early return or blow up). Maybe this already exists!
Too much indentation makes languages unfun (see every lisp struggle with this with name binding)
I honestly am a bit frustrated with Rusts pattern matching ergonomics, cuz it ends up requiring me to introduce indentation in places where I don’t want it, mainly to futz around with unwrapping structures or the like.
Lots of helper methods exist on standard library enumerations but I really want there to be some nice built in macros to just give me the innards when I need it, and have a control flow block for when I _cant_ pattern match (and thus want to do an early return or blow up). Maybe this already exists!
Too much indentation makes languages unfun (see every lisp struggle with this with name binding)
You mean like ‘matches!’?
https://doc.rust-lang.org/std/macro.matches.html
https://doc.rust-lang.org/std/macro.matches.html
What I want is..
let MyEnum(foo, bar, baz) = input but otherwise { do some stuff return some value }
Point being I don’t have to check for MyEnum-ness twice (like with matches! + if let) and the indented code is for the exception (no match) not the norm.
Tho I … guess !matches(MyEnum{ .. }) is not the worst… but it’s annoying to have this pattern all over my code instead of as some encoded concept we all use
let MyEnum(foo, bar, baz) = input but otherwise { do some stuff return some value }
Point being I don’t have to check for MyEnum-ness twice (like with matches! + if let) and the indented code is for the exception (no match) not the norm.
Tho I … guess !matches(MyEnum{ .. }) is not the worst… but it’s annoying to have this pattern all over my code instead of as some encoded concept we all use
You were pretty close with your hypothetical syntax! You can write:
let Some(y) = x else { do_stuff(); return };
I think it was a fairly recent addition to Rust.Thank dog for that. I feel like I even read the release notes for that but it was while I hadn’t been touching a specific codebase with loads of early returns. Thanks for pointing this out!
let-else was stabilised in 1.65.0, released 2022-11-03.
What determines whether `let Some(y) = x` evaluates to boolean true?
The fact that the pattern matches.
Here the type being matched is:
Here the type being matched is:
enum Option<T> {
None,
Some(T)
}
let x = Some(42);
let Some(y) = x else { panic!("None") };Use combinators!
I don't understand why go through the complication of giving this a new name (i.e. "Restructuring Pattern") where the second explanation seems more intuitive and correct.
"Restructuring Pattern" sounds like you can put back together a struct from its constituents, but that's done by normal struct instantiation.
"Restructuring Pattern" sounds like you can put back together a struct from its constituents, but that's done by normal struct instantiation.
The author is talking about restructuring syntax in the destructing match.
Rust Has this with "match" but only via a single ref.
You would have to make alternative restructuring syntax to go into the match expression if you want it to work.
Hypothetically:
I would say it's more syntactic sugar as you can do the restructuring on the right side of the match expression. You can make up an entire functional language using this technique and eliminate the => and everything after it.
I largely agree with you that it's not exactly necessary to do this. Arguably it makes things harder to read.
Rust Has this with "match" but only via a single ref.
You would have to make alternative restructuring syntax to go into the match expression if you want it to work.
Hypothetically:
fn g(v: Option<i32>) -> Option<Option<i32>> {
match v {
Some(_Some(_Some(x))),
None(_None)
}
}
Any character with an underscore is a "restructuring" syntax here. Rust only does restructuring with references via the ref keyword and it only does it once per var per expression. The syntax I presented here is hypothetical of course.I would say it's more syntactic sugar as you can do the restructuring on the right side of the match expression. You can make up an entire functional language using this technique and eliminate the => and everything after it.
I largely agree with you that it's not exactly necessary to do this. Arguably it makes things harder to read.
The author talks about adding structure, and I've been puzzled about what they are talking about until realizing that they apparently mean that a reference to something is "more" than the something hence it "adds structure". That doesn't make sense to me. More structure is if there are more elements in the data (more fields in a struct, or more alternatives in an enum), whereas a reference is just an indirection of access.
Or am I missing something?
Or am I missing something?
&T, &mut T, and T are all different types in a sense. Hence ref is transmuting types during restructuring.
Really, they are adding a new view, so perhaps this is like Haskell lenses, where you can make views with a different type that acts on underlying data
Really, they are adding a new view, so perhaps this is like Haskell lenses, where you can make views with a different type that acts on underlying data
The set of all &T is larger than the set of T.
Let’s consider the case where T is u8. There are 256 inhabitants of the u8 type (0u8-255u8).
Now consider &u8. Each u8 referent is still some value between 0 and 255, but the number of references you could have is dependent on the pointer width of your target system — let’s say 64 bits. So that’s 2^8 possible u8 values times 2^64 possible references, or 2^72 inhabitants. Well, minus one to account for null, and I’m probably missing other details, but you get the idea.
So going from T to &T introduces structure in the same sense that going from T to (T, U) does; and going from &T to T destructures in the same sense that going from (T, U) to T does. That structure may not be directly observable (as in the case of a reference: you’d have to go out of your way to observe the underlying address), but conceptually the structure is there nonetheless.
Edit:
Another way to look at it is this:
Consider a Rust “newtype”, like struct Days(u8).
If you pattern match on that to obtain the inner u8, we call that destructuring. Destructuring is to reduce or break down the structure of a thing, so the opposite must be building up structure, or “restructuring”. Therefore going in the opposite direction — stuffing a u8 value in a Days - must be restructuring. In this case both types have the same number of inhabitants (256); regardless, one direction is destructuring and the other direction is restructuring.
Let’s consider the case where T is u8. There are 256 inhabitants of the u8 type (0u8-255u8).
Now consider &u8. Each u8 referent is still some value between 0 and 255, but the number of references you could have is dependent on the pointer width of your target system — let’s say 64 bits. So that’s 2^8 possible u8 values times 2^64 possible references, or 2^72 inhabitants. Well, minus one to account for null, and I’m probably missing other details, but you get the idea.
So going from T to &T introduces structure in the same sense that going from T to (T, U) does; and going from &T to T destructures in the same sense that going from (T, U) to T does. That structure may not be directly observable (as in the case of a reference: you’d have to go out of your way to observe the underlying address), but conceptually the structure is there nonetheless.
Edit:
Another way to look at it is this:
Consider a Rust “newtype”, like struct Days(u8).
If you pattern match on that to obtain the inner u8, we call that destructuring. Destructuring is to reduce or break down the structure of a thing, so the opposite must be building up structure, or “restructuring”. Therefore going in the opposite direction — stuffing a u8 value in a Days - must be restructuring. In this case both types have the same number of inhabitants (256); regardless, one direction is destructuring and the other direction is restructuring.
I think it's “more” in the same sense that Some(x) is more than x. You can destructure &x in exactly the same way.
I think there needs to be a "center the page" browser extension because this is just absurd: https://imgur.com/a/HZqaA4V
Text becomes hard to read when lines are much over 70 characters. I don't know that there's a universally preferred solution. Personally, I only use movie-shaped windows for movies, and page-shaped windows for everything else.
I always thought it was weird when people have websites like this, anyways, for those phased away and want to read this article while centered, execute in the console:
document.getElementsByTagName('body')[0].children[0].style.margin = '0 auto';I'll go ahead and promise to leave the '#wrapper' id on that element in my blog template, so this can be shortened to:
document.getElementById('wrapper').style.margin = '0 auto';
Though if you're in the habit of customizing sites you read, I'd personally recommend using a browser extension like Stylus (https://add0n.com/stylus.html) to do CSS, so you could write it like this: #wrapper {
margin: 0 auto;
}That's a good tip! I also think its just as weird when sites have zero links to their home page. On what planet is "go to the address bar and delete the last part of the url and hit enter to go to the index" good user experience?
Agreed. It was a good tip.
My goal in leaving out the link to my home page was to make the Writing Gaggle home page more prominent than my personal blog. (I manage hosting both, see.) I only added my blog index page as an afterthought when I saw a ton of 404 errors on my server a few months ago because people were manually navigating to it.
I'll consider adding actual navigation to my page template though, so maybe you'll be less annoyed ;)
My goal in leaving out the link to my home page was to make the Writing Gaggle home page more prominent than my personal blog. (I manage hosting both, see.) I only added my blog index page as an afterthought when I saw a ton of 404 errors on my server a few months ago because people were manually navigating to it.
I'll consider adding actual navigation to my page template though, so maybe you'll be less annoyed ;)
Firefox has built-in reader view, and whatever the Chrome equivalent is.
Ctrl+Shift+K for the web console, then `document.body.style.display = "flex"; document.body.style.justifyContent = "center";`.
This sounds like view patterns in Haskell / active patterns in F#
nice reading!