Live data from Hacker News

Rust's Ugly Syntax (2023)

matklad.github.io

51–60 of 171 posts

Re: Rust's Ugly Syntax (2023)

#52
post #46
post #40

Earlier quoted context omitted.

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(…

I do remember the compiler constantly suggesting lifetimes to me as a newcomer to the language, so it didn't really feel that opt-in. Quite a lot of the suggestions also started to look like someone poured alphabet soup all over the code.

That's mostly because borrows are a curiosity of Rust that newcommers are quickly introduced to while they are mostly just a perfomance gimmick.

If you come to rust from high level language you can just do everything with Rc and cloning.

It's still hard because Rust, in opposition to every pipular language, is a value oriented language. But at least you won't have much contact with alphabet soup.

Re: Rust's Ugly Syntax (2023)

#54
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.

Here's a nice example of a Trait that has async functions:

    fn list_items(
        &'life0 self,
        collection_href: &'life1 str,
    ) -> Pin, Error>> + Send + 'async_trait>>
    where
        Self: 'async_trait,
        'life0: 'async_trait,
        'life1: 'async_trait,

Rendered docs: https://mirror.whynothugo.nl/vdirsyncer/v2.0.0-beta0/vstorag...

Source: https://git.sr.ht/~whynothugo/vdirsyncer-rs/tree/v2.0.0-beta...

Re: Rust's Ugly Syntax (2023)

#55
post #46
post #40

Earlier quoted context omitted.

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(…

I do remember the compiler constantly suggesting lifetimes to me as a newcomer to the language, so it didn't really feel that opt-in. Quite a lot of the suggestions also started to look like someone poured alphabet soup all over the code.

That's because the code triggering compilation error is using reference. If you use Rc or Arc (which pays runtime cost) there should be no lifetime at all.

Albeit I admit there somewhat exists a community sentiment like "if you use Rust, you should maximize its zero cost abstraction feature so lifetime is good and generics good", and my (minor) opinion is that, it's not always true to all users of Rust.

And the clumsy Arc>> makes users feel bad about using runtime cost paid types. Maybe we should introduce easy Rust dialect which transpiles into Rc/Clone everywhere but I doubt it's trivial to transpile.

Re: Rust's Ugly Syntax (2023)

#56

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…

Rust target user is C/C++ developer. not using brace is out of options.

Do you think C++ programmers are incapable of learning a language without braces?

Re: Rust's Ugly Syntax (2023)

#58
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.

Here's a nice example of a Trait that has async functions: fn list_items ( &'life0 self, collection_href: &'life1 str, ) -> Pin , Error>> + Send + 'async_trait>> where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, Rendered docs: https://mirror.whynothugo.nl/vdirsyncer/v2.0.0-beta0/vstorag... Source: https://git.sr.ht/~whynothugo/vdirsyncer-rs/tree/v2.0.0-beta...

> https://mirror.whynothugo.nl/vdirsyncer/v2.0.0-beta0/vstorag...

Unrelated to parent but maybe relevant to you: Rust API naming guidelines say no `get_` prefix on getters.

https://rust-lang.github.io/api-guidelines/naming.html#gette...

Re: Rust's Ugly Syntax (2023)

#59
post #36

Earlier quoted context omitted.

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 reas…

Then if Path is not about abstraction, why not use a raw byte slice like &[u8]

That's orthogonal. If the type was `&[u8]` instead of `Path` the type signature would be:

    pub fn read>(path: P) -> Result>
The reasons for it to be generic and us `AsRef` remain. The reason for Path over &[u8] is, AFAIK, because not all byte slices are valid paths on all OSs, but also because a dedicated type lets the standard library add methods such as `Path::join`

Re: Rust's Ugly Syntax (2023)

#60

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…

I strongly support your point, but the example is still sand-in-the-eyes for me. I hold that one symbol should not alter the semantics of a program and there should never ever be sequences of one-symbol syntactic elements.

In an Ada-like language, it would be something like

  generic
      type Path_Type implements As_Path_Ref;
      type Reader implements IO.File_Reader;
  function Read(Path: Path_Type) return Reader.Result_Vector_Type|Reader.Error_Type is
      function Inner_Read(P: Path) return Read'Result_Type is
      begin
          File:  mutable auto := try IO.Open_File(P);
          Bytes: mutable auto := Reader.Result_Vector_Type.Create();
          try Reader.Read_To_End(File, in out Bytes);
          return Bytes;
      end;
  begin
      return Inner_Read(Path.As_Ref());
  end Read;
Post reply on HN