Live data from Hacker News

Rust's Block Pattern

notgull.net

61–70 of 121 posts

Re: Rust's Block Pattern

#61

Earlier quoted context omitted.

So many options why oh why. let run with also apply

Each does different things, and Rust also has plenty of them. and_then(), or(), or_else(), then(), the list goes on. Kotlin just implements them more widely. Actually, Kotlin's with() and apply() are more powerful than what Rust can provide. Then again, Rust isn't designed with OO in mind, so you probably shouldn't use those patterns in Rust anyway.

I think you've misunderstood the point they were making by addressing the number as if it was the only concern and then only mentioning the actual point they were trying to make as if it were an incidental afterthought. I don't think it's likely they're criticizing five functions in the standard library is too many, but that having five special functions with certain semantics that only apply to them is too many. The methods you mention in Rust are all in the first category; you could easily write them yourself for any type you define without needing to resort to wrapping any of them. It's not clear to me that someone could write a function in Kotlin with special scoping semantics around an object without resorting to wrapping one of those functions.

Re: Rust's Block Pattern

#62
For those who might not have seen it, you can use this to make a `while` act like a `do-while` loop by putting the entire body in the boolean clause (and then putting an empty block for the actual body):

    // double the value of `x` until it's at least 10
    while { x = x * 2; x 
This isn't something that often will end up being more readable compared to another way to express it (e.g. an unconditional `loop` with a manual `break`, or refactoring the body into a separate function to be called once before entering the loop), but it's a fun trick to show people sometimes.

Re: Rust's Block Pattern

#63

I often employ this pattern in Ruby using `.tap` or a `begin` block. It barely adds any functionality but it's useful for readability because of the same reasons in the OP. It helps because I've been bitten by code that did this: setup_a = some_stuff setup_b = some_more_stuff i_think_this_is_setup = even_more_stuff the_thing = run_setup(setup_a, setup_b, i_think_this_is_setup) That's all fine until later on, probably…

JavaScript chiming in...

    function abc() {
       let a = 1
       {
         let b = 2
       }
       console.log(typeof a)      
       console.log(typeof b)
     }
     abc()
Used to do this occasionally for exactly the same reasons- don't leave dangling variables junking up your scope, and don't make weirdo functions with parameter passing that you'll only ever call once!

Re: Rust's Block Pattern

#64
post #12

There are some situations with tricky lifetime issues that are almost impossible to write without this pattern. Trying to break code out into functions would force you to name all the types (not even possible for closures) or use generics (which can lead to difficulties specifying all required trait bounds), and `drop()` on its own is of no use since it doesn't effect the lexical lifetimes.

Conversely, I use this "block pattern" a lot, and sometimes it causes lifetime issues: let foo: &[SomeType] = { let mut foo = vec![]; // ... initialize foo ... &foo }; This doesn't work: the memory is owned by the Vec, whose lifetime is tied to the block, so the slice is invalid outside of that block. To be fair, it's probably best to just make foo a Vec, and turn it into a slice where needed.

Unless I'm misunderstanding, you'd have the same lifetime issue if you tried to move the block into a function, though. I think the parent comment's point is that it causes fewer issues than abstracting to a separate function, not necessarily compared to inlining everything.

Re: Rust's Block Pattern

#65
post #58
post #51

Earlier quoted context omitted.

Can this just be done as a lambda that is immediately evaluated? It's just much more verbose. let x = (|| -> Result { Ok("1".parse:: ()? + "2".parse:: ()? + "3".parse:: ()?) })();

My instinct is this would get hairy much faster if you want to actually close over variables compared to using a block.

If the verbose return type syntax can't be elided, I think it's more or less dead as a pattern.

Re: Rust's Block Pattern

#66
post #51

I have one better: the try block pattern. https://doc.rust-lang.org/beta/unstable-book/language-featur...

Can this just be done as a lambda that is immediately evaluated? It's just much more verbose. let x = (|| -> Result { Ok("1".parse:: ()? + "2".parse:: ()? + "3".parse:: ()?) })();

That prevents other control flow mechanisms (return, break) from operating past the function boundary. In general, I avoid single-callsite functions as much as possible (including the iterator api) for this reason.

Re: Rust's Block Pattern

#67
From the article:

  Here’s a little idiom that I haven’t really seen discussed 
  anywhere, that I think makes Rust code much cleaner and 
  more robust.
  
  I don’t know if there’s an actual name for this idiom; I’m 
  calling it the “block pattern” for lack of a better word.
This idiom has been discussed and codified in various languages for many years. For example, Scala has supported the same thusly:

  val foo: Int = {
    val one = 1
    val two = 2

    one + two
  }
Java (the language) has also supported[0] similar semantics.

Good to see Rust supports this technique as well.

0 - https://docs.oracle.com/javase/tutorial/java/javaOO/initial....

Re: Rust's Block Pattern

#70
post #39

I use this all the time. It's features like these that sell Rust for me honestly; even if you wrapped your whole program in `unsafe` it would still be a massively better language than C++ or C.

Many languages use this idiom. Some popular ones even. So while it's good that Rust joined them, it's hardly a differentiator.
Post reply on HN