Things you thought were awful but changed your mind about

Simpler, yes, but I’m not completely convinced about better for certain niche uses: Cell<Option<T>> can’t panic, and trades the overhead of RefCell’s bookkeeping for the overhead of dealing with Option (including the potential of reading None when you expected Some).

For everything except the most performance-critical sections, both overheads will be lost in the noise. So the decision comes down to one of semantics: Do you conceptually have a single item with controlled access, or do you conceptually have a storage slot that is sometimes empty. In practice, this will depend on how you usually want to handle access not being available.

5 Likes

In the prehistoric Rust days, Cargo used bash scripts instead of build.rs.

I was pretty annoyed when bash support got dropped, because I had to rewrite my Makefiles in Rust, and Rust is not a build DSL, not even a scripting language.

However, in the end I like it. Ability to use cratesio packages in build.rs is pretty awesome. It means that build scripts can defer hard parts to well-tested reusable packages instead of reinventing the same patterns/workarounds for every quirk of every platform. Being bash-free means most packages work fine on Windows out of the box too (not just WSL VM). In my C projects I avoided having Windows support because it was pretty hard and annoying, but in pure-Rust projects it mostly just works.

19 Likes

I'm glad someone fought for it. That was absolutely worth fighting for.

It doesn't surprise me that people like await as object notation. There are a lot of things that would be more ergonomic if they came after a piece of code, but programmers are always demanding operators be placed to the left instead, due to I guess the structure of the english language? ("throw ball", "match expression"). Everyone loved object dot notation once it was discovered, but they stop there without considering other benefits that could be had if they took that idea to its conclusion.

foo.match {
  Some(x) => x,
  None => 0,
}.map(|x| x + 1)

Or perhaps any arbitrary function could be used with dot object notation if you assume that their first argument is special (in traits it is so special it has its own &self syntax sugar). This is done in some langauges with a pipe operator, but there's no reason you couldn't just reuse object dot notation.

let date = self.date().deref().std::str::from_utf8()?.NaiveDate::parse_from_str("%F")
// as opposed to
let date = NaiveDate::parse_from_str(std::str::from_utf8(&self.date())?, "%F")?;

// or even ifs, provided bar is boolean
bar.if {
  "everything is ok"
} else {
  "impending disaster"
}.print!()

I believe this would be a huge change that would simplify greatly many libraries out there that have huge traits with every possible method you could call on them seemingly purely for ergonomic reasons.

Most of us read from left to right, so it only makes sense to have code flow from the left to the right. Once you become aware of this fact, you realize that in most code you see your eyes search for something in the middle and zig zag back and forth trying to find the next piece.

9 Likes

One important reason is that Rust tried to use mimicry to look like C++. What you are proposing moves it too far from that.

It may sound silly, but it's not. Lots of people just flat out reject an idea to learn Ada or Haskell simply because they are not curly-bracket languages.

Rust have to use its “weirdness budget” really carefully.

But yeah, this may be a good idea, in the end.

3 Likes

You shouldn't have posted that, because now I will never be able to come to terms with the fact that we don't have that. (googles "How to forget that a better syntax is possible".) :frowning:

Related to the thread's subject: You just made me stop disliking x.sin(), which I've really disliked up until this point.

Can we have a Rust Extreme Dot Edition, which takes this syntax to its extreme?

Oh; not silly at all -- I know people who refuse to use Python because of the indentation. And wrt to the x.sin() thing -- I'm not exaggerating when I say that made me pause and wonder if Rust was too weird for me.

3 Likes

The big difference between Python and Haskell is that Haskell is statically-typed, so if you mess up indentation, it fails to compile instead of silently misbehaving at runtime. Thus indentation is a real problem in Python (especially when copy-pasting code from an exteral source or during refactoring, for example).

Note that you don't have to write that. Thanks to UFCS, you can write f64::sin(x) – in fact, I strongly prefer that.

As for the postfix match and postfix-everything: I never understood why people want it, and I still find postfix .await really distasteful even after having used it. It's nothing like a method call or a field, nor is match or any other built-in language construct, and making them look like one is just actively misleading.

And no, chaining is simply not a problem that needs to be solved. It can be entirely and trivially avoided with the current toolset of the language, using the revolutionary idea of variable bindings.

4 Likes

Most people who I know perceive it exactly like a method call. Yes, weird method call, which forces your asynchronous procedure to sleep a bit while it's waiting for a result, but still a, conceptually, method call.

Sure, but prefix await still break the logic. First we turn address of database into connection, then wait for the answer, then query that connection and wait again… why similar steps are looking so different? await is a method call in my head. Very similar one to unwrap. They even can, both, cause the rest of the code to never execute.

1 Like

I have a positive bias for what used to be explicit use of the try macro, now the ?. However, the need to map different Result types remains awkward for me. I have not experienced anything better in other languages but, I keep thinking there has to be a better way in Rust given the elegance of the design for Iteration.

I worked with C in the embedded-chip context but not ever with C++. I was also familiar working with a strongly typed language working in Haskell. This might explain why I keep having to remind myself that &mut T is a different type than &T and T (where of course T can represent all of these).

Perhaps this internal "friction" is good, like having to explicitly call .collect() or .await. I like having to be explicit here because it informs how/when memory/resources are being consumed and expected to return.

That all said, I suspect there is a more manageable way to be express a generic implementation when it's safe to do so for all "three versions" of the "core" (maybe?) type. This is perhaps something GADs will help with?

I still have a little PTSD working on a multi-threaded, SIMD-optimized data processing code base in Rust that, in what seemed like "all of a sudden", got really complicated really fast dealing with "3" of what seemed like everything. That was more than a year ago, but still has me hold back a bit on where to use Rust (totally a skill-set that is on me to develop, so not a "diss", but on topic)

2 Likes

I would write this as:

bar then {
  "everything is ok"
} else {
  "impending disaster"
}.print()

Forth does it like this:

DAY? 32 < IF 
  ." Looks good "
ELSE 
  ." no way "
THEN

Choice without if just doesn't make much sense, but the idea is similar.

It actually pretty logical but adding something like that to Rust wouldn't make sense.

Overuse of weirdness budget, I'll say.

1 Like

For reference, here's current stable Rust:

let message = bar.then(|| {
    "everything is ok"
}).unwrap_or_else(|| {
    "impending disaster"
});
println!("{message}");
1 Like