Live data from Hacker News

Rust's Ugly Syntax (2023)

matklad.github.io

31–40 of 171 posts

Re: Rust's Ugly Syntax (2023)

#31
post #2

I think the article makes a good point, but the actual example isn’t Rust’s worst, not even close. It gets really hard to follow code when multiple generic types are combined with lifetime markers. Then it truly becomes a mess.

I always, always forget what `'a: 'b` means, because I remember it always being the opposite of what I think it is, but memorizing that obviously doesn't work because then it will just flip again the next time. It's so annoying.

If you think of it as 'a implements b', it makes sense for both lifetimes and (other) subtypes: If lifetime `a` implements `b`, it is obviously valid for `b` (and maybe longer).

Re: Rust's Ugly Syntax (2023)

#32

Kinda disingenuous, you don't reskin one language in another to make an argument about syntax -- you develop a clear syntax for a given semantics. That's what rust did not do -- it copied c++/java-ish, and that style did not support the weight. When type signatures are so complex it makes vastly more sense to separate them out, Consider, read :: AsRef(Path) -> IO.Result(Vec(U8)) pub fn read(path): inner :: &Path -> I…

People may disagree on specifics, but you're definitely right that being able to separate the function signature from its definition would be very helpful in complex cases.

Re: Rust's Ugly Syntax (2023)

#33
> The next noisy element is the > constraint. It is needed because Rust loves exposing physical layout of bytes in memory as an interface, specifically for cases where that brings performance. In particular, the meaning of Path is not that it is some abstract representation of a file path, but that it is just literally a bunch of contiguous bytes in memory.

I can't understand this. Isn't this for polymorphism like what we do this:

```rust fn some_function(a: impl ToString) -> String { a.to_string(); } ```

What to do with memory layout? Thanks for any explanation.

Re: Rust's Ugly Syntax (2023)

#34
post #4

Earlier quoted context omitted.

Agree about the example! I can't tell if this article is tongue-in-cheek or earnest. I'm unclear on the point the author is trying to make.

My reading is: people use Rust because it’s fast but then they complain about the semantics that make it fast. In other words, be careful what you wish for. Most people would probably be better served by a language that was a tiny bit slower but had better developer productivity. However, once you deviate from the goal of “as fast as possible”, then you have to choose which parts you want to sacrifice speed for produ…

> people use Rust because it’s fast but then they complain about the semantics that make it fast.

I don't think most people use Rust because it's fast - fast is nice but Rust is being thrown at a bunch of use cases (e.g. backend services and APIs) for which it replaces "slower" garbage collected languages (the language being faster doesn't always make the overall product/service faster but that's a separate question).

What Rust gives you is a viable potential alternative to C and C++ in places where you absolutely can't have a GC language, and that's a huge deal, the problems and confusion start when people try to use Rust for everything.

> everybody agrees that Rust is too complicated

I don't think this is true either - a large part of the Rust community seem to think that it's as complicated as it needs to be. As a beginner/outsider, I found it kind of cumbersome to get started with, but that's certainly not everyone's opinion.

> Most people would probably be better served by a language that was a tiny bit slower but had better developer productivity.

True, and such languages already exist and are widely used, Rust doesn't need to fit that use case.

Re: Rust's Ugly Syntax (2023)

#35
post #19

The final version is still ugly. Why `pub fn'? Why is public not the default and why do you have to specify that it's a function? Why `: type' and `-> type', why can't type go before the identifier? Why do you need `File::' and `Bytes::'? What is that question mark? Why does the last statement not need a semicolon? It's like the opposite of everything people are used to.

Your points have nothing to do with ugliness. > Why `pub fn'? Why is public not the default and why do you have to specify that it's a function? If public were the default, you'd end up having to make other functions `priv fn` instead. > Why `: type' and `-> type', why can't type go before the identifier? It's easier to parse, and most major typed languages other than C/C++/C#/Java put the type after the identifier.…

> If public were the default, you'd end up having to make other functions `priv fn` instead.

My guilty pleasure is Go's visibility system, where all functions that start with lowercase are private to the scope of the current class/file and all Functions that start with Uppercase are public.

It doesn't look but it would work and it's a mess when you need acronyms, but it somehow works great and the result looks nice.

Re: Rust's Ugly Syntax (2023)

#36

> The next noisy element is the > constraint. It is needed because Rust loves exposing physical layout of bytes in memory as an interface, specifically for cases where that brings performance. In particular, the meaning of Path is not that it is some abstract representation of a file path, but that it is just literally a bunch of contiguous bytes in memory. I can't understand this. Isn't this for polymorphism like wh…

Rust needs to know the exact size, layout, and alignment of every argument passed to a function to determine how it gets passed(register(s) or spilled to stack) and used. For example PathBuf and String can both be turned into a reference to a Path, and while they have the same size their layout and implementation of `as_ref` differ.

As for `impl`,

    fn foo(a: impl ToString)
is syntactic sugar for

    fn foo(a: S)
The reason the standard library doesn't use this is because the code predates the introduction of `impl` in argument position.

The reason the function takes `AsRef` instead of `&Path` is callsite ergonomics. If it took `&Path` all callsites need to be turned into `read(path.as_ref())` or equivalent. With `AsRef` it transparently works with any type that can be turned into a `&Path` including `&Path` itself.

Re: Rust's Ugly Syntax (2023)

#37
post #27

Earlier quoted context omitted.

My reading is: people use Rust because it’s fast but then they complain about the semantics that make it fast. In other words, be careful what you wish for. Most people would probably be better served by a language that was a tiny bit slower but had better developer productivity. However, once you deviate from the goal of “as fast as possible”, then you have to choose which parts you want to sacrifice speed for produ…

> Most people would probably be better served by a language that was a tiny bit slower but had better developer productivity. D maybe? D and Rust are the two languages which come to mind when I think about "possible C++ replacements".

When GP said “most”, I interpreted it more broadly. Most applications simply do not require the guarantees of a non-GC language. When you expand that horizon, list of contenders becomes considerably larger - even when restricted to statically typed languages.

Re: Rust's Ugly Syntax (2023)

#38
post #31

Earlier quoted context omitted.

I always, always forget what `'a: 'b` means, because I remember it always being the opposite of what I think it is, but memorizing that obviously doesn't work because then it will just flip again the next time. It's so annoying.

If you think of it as 'a implements b', it makes sense for both lifetimes and (other) subtypes: If lifetime `a` implements `b`, it is obviously valid for `b` (and maybe longer).

I always do `` etc for generics, but for lifetimes it's `` which always trips me up...

Re: Rust's Ugly Syntax (2023)

#40
post #2

I think the article makes a good point, but the actual example isn’t Rust’s worst, not even close. It gets really hard to follow code when multiple generic types are combined with lifetime markers. Then it truly becomes a mess.

IMHO the mentioned examples of complexity like multiple type variables and lifetimes with bounds are for who "really" wants compile-time contracts. These are mostly opt-in so higher level use cases(like writing backend business logics) should not care about that, just wrapping everything with Boxes and Arcs.

Of course Rust is not perfect; there is some 'leakages' of low level aspects to high level like async caveats(recursion, pinning, etc.). I'm not sure how these can be avoided. Maybe just trial-and-errors for all..?

Post reply on HN