this post was submitted on 24 Jun 2024
36 points (90.9% liked)

Rust

6035 readers
3 users here now

Welcome to the Rust community! This is a place to discuss about the Rust programming language.

Wormhole

[email protected]

Credits

  • The icon is a modified version of the official rust logo (changing the colors to a gradient and black background)

founded 1 year ago
MODERATORS
top 19 comments
sorted by: hot top controversial new old
[–] 5C5C5C 13 points 5 months ago* (last edited 5 months ago)

The ideas in the article are great, I'm just a little confused by some aspects of the author's tone where it sounds like there's an assumption that the Rust community isn't interested in expanding the scope of the language to every conceivable use case domain and height.

For the 4 years that I've been paying attention the Rust language is advancing faster than I ever thought a language is able to, but more importantly that advancement has been sound and sensible. So far I haven't seen a language feature make it into Rust stable and thought "Oh no that was a mistake", even as features roll in at an incredible rate.

Compare that to the C++ ecosystem where I feel like almost every new language feature is arriving very slowly while also being poorly executed (not that I think the ISO committee is doing their job badly; I just think it's effectively impossible to make new language features in C++ without gross problems so long as you demand backwards compatibility).

I fully expect everything in this very sensible list to make it into the language at a reasonable pace. I don't object to the "bikeshedding" as much as the author here seems to because I'd appreciate if Rust can avoid painting itself into a corner with bad language design choices the way C++ has. If we're talking about language ergonomics, I'd rather suffer some tedium now while waiting for a feature to be polished than be stuck in a corner forever in the future because a bad decision was made.

One example I can think of is I'm not convinced that his proposal around kwargs for function arguments is a good thing, at least not without some serious thinking. For example should it support the ability to reduce foo(a, b, x: x) to just foo(a, b, x) like what's done for struct construction? If so then the optional arguments start to look too much like positional arguments and the syntax starts to get questionable to me. On the other hand if that simplification isn't supported then that becomes inconsistent with other parts of the language. So this is something that I believe requires a lot of serious thought, and maybe the better answer is to have built-in macros for generating builder structs

That being said, the edition system of Rust could afford us some leeway on not being forever trapped with a bad language design choice, but I don't think we want to rely too much on that.

[–] BB_C 8 points 5 months ago

But why can’t we fight to make Rust better and be that “good enough” tool for the next generation of plasma physicists, biotech researchers, and AI engineers?

Because to best realize and appreciate Rust's added value, one has to to be aware, and hindered by, the problems Rust tries to fix.

Because Rust expects good software engineering practices to be put front and center, while in some fields, they are a footnote at best.

Because the idea of a uni-language (uni- anything really) is unattainable, not because the blasé egalitarian "best tool for the job" mantra is true, but because "best tool" from a productive PoV is primarily a question of who's going to use it, not the job itself.

Even if a uni-language was the best at everything, that doesn't mean every person who will theoretically use it will be fit, now or ever, to maximize its potential. If a person is able to do more with an assumed worse tool than he does with a better one, that doesn't necessarily invalidate the assumption, nor is it necessarily the fault of the assumed better tool.

Rust’s success is not a technical feat, but rather a social one

fighting the urge to close tab

Projects like Rust-Analyzer, rustfmt, cargo, miri, rustdoc, mdbook, etc are all social byproduct’s of Rust’s success.

fighting much harder

LogLog’s post makes it clear we need to start pushing the language forward.

One man's pushing the language forward is another man's pushing the language backward.

A quick table of contents

Stopped here after all the marketing talk inserted in the middle.
May come back later.


Side Note: I don't know what part of the webshit stack may have caused this, but selecting text (e.g. by triple-clicking on a paragraph) after the page is loaded for a while is broken for me on Firefox. A lot of errors getting printed in the JS console too. Doesn't happen in a Blink^twice^ browser.

[–] [email protected] 8 points 5 months ago (2 children)

This article starts off as a response to another article, but doesn't link to the article it is talking about! I found that frustrating and poor form, community-wise.

[–] davawen 7 points 5 months ago

Given it talks about gamedev at the start, I'm pretty sure it's this one: https://loglog.games/blog/leaving-rust-gamedev/

It's indeed very well written and throughout.

[–] sudo 1 points 5 months ago

Yeah I have no idea where that article is or even the title. "LogLog Rust" isn't a good enough search term.

[–] [email protected] 7 points 5 months ago* (last edited 5 months ago)

Ignoring the rest, just some thoughts about the list of proposed features:

A capture trait for automatic cheap clones

Automatic implicit cloning would be useful for high level developers, but not ideal at all for low level or performance-sensitive code. It's not the case that anyone using a shared pointer wants to clone it all the time. The high level usecase doesn't justify the cost assumed by the low level users.

Instead, being able to wrap those types with some kind of custom "clone automatically" type feels like a middle ground. It could be a trait like mentioned, or a special type in the standard library. Suppose we call it Autoclone[T] or something (using brackets because Lemmy nonsense). Autoclone[Rc[T]] could function like the article mentioned.

Automatic partial borrows for private methods

Having "private" and non-"private" methods function differently feels like confusing behavior that should be avoided if possible. Also, "private" I assume refers to pub(self) methods (the default if unspecified), which is "module-level" methods (so accessible within the module it's defined in). Anyway, there are years of discussion around this so I'll just defer to that as to why it's not in yet.

I agree with the urge to make it happen though. Some method of doing partial borrows for methods would be nice.

Named and optional function parameters

This is what prompted me to even comment. What "every language" does for complex constructors is different per language. C#, for example, supports both named and optional parameters, but construction usually uses an object initializer:

var jake = new Person("Jake")
{
    Age = 30,
    // ...
};

This is similar to Rust's initializers:

let jake = Person {
    age: 30,
    ...Person::new("Jake")
};

Where it gets tricky is around required parameters. Optional ones don't really matter since you can use the syntax above if you want, or chain methods like with the builder style.

As for the overhead of writing builders, there's already libraries that let you slap #[derive(Builder)] on types and get a builder type automatically.

As for optional parameters, how those are implemented differs between languages. In C#, default values must be constant values. In Python, default values are basically "global" values and this nonsense is possible:

def count_calls(count=[]):
    # if unset, count is a global list
    count.push(0)
    return len(count)

Anyway, all this is to say that the value of optional parameters isn't obvious.

Named parameters is more of a personal choice thing, but falls apart when your parameter has no name and is actually a pattern:

async fn get_foo(_: u32) {}

Also, traits often use names prefixed with underscores in their default fn impls to indicate a parameter an implementer has access to, but the trait doesn't use by default. Do you use that name, or the name the implementer defined? I assume the former since you don't always know the concrete type.

Faster unwrap syntax

We have that, it's called the try operator.

Okay I know it's different, and I know everyone's use case is different, but I've been coding long enough to know that enabling easy unwraps means people will use it everywhere despite proper error handling being pretty dang important in a production environment.

Thinking of my coworkers alone, if we were to start writing Rust, they'd use that operator everywhere because that's what they're familiar with coming from other languages. Then comes the inevitable "how do I add a try-catch block?" caused by later needing to handle an error.

Anyway, I prefer the extra syntax since it guides devs away from using that method over propagating the error upwards. For the most part, you can just use anyhow::Result and get most error types converted automatically.

Try trait

Yes please.

Specialization

Yes please.

Stabilizing async read/write traits to standardize on an executor API

I'd want input from runtime devs on this, but if possible, yes please.

Allowing compilation of builds that fail typechecking

???

How is the compiler going to know how to compile the code if it doesn't know the types? This isn't Python. The compiler needs to know things like how much memory to allocate, and there's a ton of potential unsound behavior that can occur from treating one type as another, even if they're the same size.

Anyway I'll save the rest for later since I'm out of time.

[–] FizzyOrange 3 points 5 months ago

I do like the author's overall point that we should try and fix the issues rather than just pretend they don't exist.

However a lot of these seem to be things that people have obviously thought of already, and they've thought about it more than the author and found problems that he just hasn't got to yet. Incremental linking for example. Yeah obvious idea but did he think about all of these issues?

Good brainstorm anyway.

[–] [email protected] 2 points 5 months ago (2 children)

There are a few very questionable things in there. Unwrap should literally never appear in production code so if your code base uses it so often that you want a short-hand syntax for it that calls into doubt everything else you wrote.

[–] [email protected] 3 points 5 months ago (2 children)

Unwrap should literally never appear in production code

Unwrap comes up all the time in the standard library.

For example, if you know you're popping from a non-empty vector, unwrap is totally the right too for the job. There are tons of circumstances where you know at higher levels that edge cases defended against at lower levels with Option cannot occur.

[–] BB_C 5 points 5 months ago* (last edited 5 months ago) (2 children)

(DISCLAIMER: I haven't read the post yet.)

For example, if you know you’re popping from a non-empty vector, unwrap is totally the right too(l) for the job.

That would/should be .expect(). You register your assumption once, at the source level, and at the panic level if the assumption ever gets broken. And it's not necessarily a (local) logical error that may cause this. It could be a logical error somewhere else, or a broken running environment where sound logic is broken by hardware or external system issues.

If you would be writing comments around your .unwrap()s anyway (which you should be), then .expect() is a strictly superior choice.

One could say .unwrap() was a mistake. It's not even that short of a shortcut (typing wise). And the maximumly lazy could have always written .expect("") instead anyway.

[–] livingcoder 0 points 4 months ago

I personally think that unwrap and the question mark operator were a mistake.

[–] [email protected] -1 points 5 months ago (2 children)

Fair. But unwrap versus expect isn't really the point. Sure one has a better error message printed to your backtrace. But IMO that's not what I'm looking for when I'm looking at a backtrace. I don't mind plain unwraps or assertions without messages.

From my experience, when people say "don't unwrap in production code" they really mean "don't call panic! in production code." And that's a bad take.

Annotating unreachable branches with a panic is the right thing to do; mucking up your interfaces to propagate errors that can't actually happen is the wrong thing to do.

[–] BB_C 3 points 5 months ago

that’s not what I’m looking for when I’m looking at a backtrace. I don’t mind plain unwraps or assertions without messages.

You're assuming the PoV of a developer in an at least partially controlled environment.

Don't underestimate the power of (preferably specific/unique) text. Text a user (who is more likely to be experiencing a partially broken environment) can put in a search engine after copying it or memorizing it. The backtrace itself at this point is maybe gone because the user didn't care, or couldn't copy it anyway.

[–] BB_C 0 points 5 months ago (1 children)

From my experience, when people say “don’t unwrap in production code” they really mean “don’t call panic! in production code.” And that’s a bad take.

What should be a non-absolutest mantra can be bad if applied absolutely. Yes.

Annotating unreachable branches with a panic is the right thing to do; mucking up your interfaces to propagate errors that can’t actually happen is the wrong thing to do.

What should be a non-absolutest mantra can be bad if applied absolutely.

[–] [email protected] 1 points 5 months ago (1 children)

You talk about "non-absolutist," but this thread got started because the parent comment said "literally never."

I am literally making the point that the absolutist take is bad, and that there are good reasons to call unwrap in prod code.

smdh

[–] BB_C -1 points 5 months ago (1 children)

Don't get angry with me my friend. We are more in agreement than not re panics (not .unwrap(), another comment coming).

Maybe I'm wrong, but I understood 'literally' in 'literally never' in the way young people use it, which doesn't really mean 'literally', and is just used to convey exaggeration.

[–] [email protected] 2 points 5 months ago

No, I actually meant it as in the traditional meaning of literally. As in

[lints.clippy]
unwrap_used = "warn"
expect_used = "warn"

along with a pre-commit hook that does

cargo clippy -D warnings

(deny warnings).

There are always better ways to write an unwrap, usually via pattern matching and handling the error cases properly, at the very least logging them.

[–] [email protected] 1 points 5 months ago

As a sysadmin I have sadly encountered too many situations where a programmer thought "oh, this will never happen" to agree with you that a code construct that provides no information to the user should be used in such cases.

[–] 5C5C5C 3 points 5 months ago

I do think that specific point is catering too much to sloppy get-it-done-fast-and-don't-think developers. It's true that they are Rust's most untapped demographic, and the language won't reach the pinnacle of mainstream usage without getting buy-in from that group, but I really think they'll be won over eventually by everything else the language and ecosystem offers, and .unwrap() won't be such an unreasonable price for them to pay in the long run.