Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Rust

Switching to a more modern topic, the introduction of the Rust language into Linux, Torvalds is disappointed that its adoption isn't going faster. "I was expecting updates to be faster, but part of the problem is that old-time kernel developers are used to C and don't know Rust. They're not exactly excited about having to learn a new language that is, in some respects, very different. So there's been some pushback on Rust." On top of that, Torvalds commented, "Another reason has been the Rust infrastructure itself has not been super stable."

AI

"When AI came in, it was wonderful, because Nvidia got much more involved in the kernel. Nvidia went from being on my list of companies who are not good to my list of companies who are doing really good work."



IMHO, as a C developer, I love Rust's features but the syntax is terrible.


Yup, Rust syntax is just the worst - except for all the other syntax proposals that have been made over time: https://matklad.github.io/2023/01/26/rusts-ugly-syntax.html


Imho quite bad example.

Not even one tubo-fish. No multiple lines of `where` clauses. No (dyn) trait impl. Not even a lambda!

On top of that Rust's syntax is full of noise. Curly braces, semicolons, ugly angel brackets, `::` instead of points for namespaces, etc.

Rust is a quite good language on the technical level. But the surface syntax is really ugly.


Another developer here who writes in C and C++, same. I think a lot of people have this complaint. I'm sad about it because what Rust provides in principle sounds great. The language is hideous though, and I really don't want to write anything in it.


What things are hideous about it and what would you rather it look like?


That video snippet which was posted by that former Rust/Linux maintainer has an excellent example:

https://www.youtube.com/watch?t=1529&v=WiPp9YEBV0Q&feature=y...

The return type of that get_or_create_inode() function is:

    Result<Either<ARef<INode<T>>, inode::New<T>>>
I'd say that's a pretty good example for 'hideous'. It looks like some of the worst C++ code I've seen and then quickly tried to forget. If stuff like this ends up in the Linux kernel I would be pissed too as a Linux kernel greybeard.


I agree that it could maybe use some new types, but it also does kinda get to the heart of this: I’ve never used this kernel API before, but I can say “oh, this function may error out, but if it doesn’t, it gives me either a new inode or a reference (I’m assuming that’s what ARef is) to an existing one. That’s a lot of valuable information, at a glance.


> That’s a lot of valuable information, at a glance

...if you know how to decode it. That example looks like one of those C++ SFINAE hacks, e.g. trying to bend the template/generics syntax beyond what it was designed for.

If Rust APIs want to go that way (of overly expressive strong typing) then Rust really should offer a better syntax for describing such types. But it's not even clear to me if such strongly typed APIs are even a good idea. From my experience in C++ and (especially) Typescript with similar APIs, it's a PITA to work with in real world projects, and on both sides of the API.


There is no fundamental way to simplify that further. If you omit angle brackets and use whitespaces instead then you will get a Haskell-like syntax which is even more horrible:

    Result (Either (ARef (INode T)) (inode::New T))
Maybe a single generic argument shouldn't need any parentheses. It would imply that `a b c` should be parsed as `a (b c)`, which is not super obvious due to the inverted order. ML instead swaps the entire order, so that `a b c` is sensibly `(a b) c` but here `b` and `c` would be base types. That yields the following:

    (T INode ARef, T inode::New) Either Result
I think this is indeed slightly better than the current Rust syntax, but any C developer will have hard time adjusting their brains to this order. This and the very existence of C++ prompted Rust to use the same syntax as C++ (the very early version used `A[B]` instead), but otherwise Rust types are much more predictable than C++ due to the lack of compile-time metaprogramming [1].

The fact that Haskell is not very different from Rust in the type syntax complexity shows that this type is just fundamentally "complex", because it is composed of multiple composable types. (I will personally avoid `Either` because I think an explicit enum is better here, but otherwise everything is independent from each other.) And an equivalent C function will do the same thing but without any type declaration and ad-hoc error code and/or convention, which never works well in practice.

[1] Some Rust crates do still attempt this, like the `typenum` crate, but I would not recommend them in general because they are really awkward to use.


How would you like Rust to describe such Types?

> a PITA to work with in real world projects, and on both sides of the API.

Don't see how this is a Typing issue, sounds more like API issue


> How would you like Rust to describe such Types?

If I knew the answer I would design programming languages ;) Replacing `Either<this, that>` with something like `this | that` like in Typescript might be going into the right direction though. Zig has similar syntax sugar for Rust's Result<> and Option<>. Such things are so fundamental that they should be part of the language instead of the stdlib IMHO.

> Don't see how this is a Typing issue, sounds more like API issue

Designing an API is a typing issue, because an API is essentially nothing more than a list of types.


Here `Result` is very likely not the standard library type, because `std::result::Result` has two generic types for Ok and Err. It will be a partial alias to `Result<T, Error>` where `Error` is used throughout the entire code base to simplify the error handling. (The same pattern occurs in `std::io::{Result, Error}` in the standard library.)

Making `Result` a built-in syntax will disallow such freedom, and Zig has whole dedicated features to tackle problems solved by having `Result` just a normal type. Everything here is just a trade-off and both Rust and Zig have reasonable solutions for their use cases.


> Replacing `Either<this, that>` with something like `this | that`

That's not the same.

The first one is an ADT instance with exactly two cases, whereas the other is an arbitrary union type.

The difference is when you pattern match on them. In the first case the compiler can statically verify that you handled all (two) cases. In the second case you can deconstruct the union, but you're not forced to handle all cases, simply because there are no know "all cases"; you could have arbitrary unions of any amount of things. (Of course the compiler could create for any usage of an union type some synthetic ADTs behind the scenes and check exhaustivity on them, but this would explode pretty quickly, I think, because of code bloat and compile times. Also that's not the expected behavior of union types in general).

> Designing an API is a typing issue, because an API is essentially nothing more than a list of types.

It's more a list of signatures, and some interaction protocol on top of that. (Frankly not expressed in types usually. We don't have simple ways to use things like session types still).

The types used in your signatures can be as specific or general as you want. It's the choice of the API designer. There is no issue with typing as such. You could just declare everything being a function from `Any => Any`… That's of course as bad as having so specific types that there can be more or less only one object shape that fits it. Classical over-typing, and that's imho just bad API design…


If you need a certain object shape, best require it in the API.

You can't be too specific there, because being as specific as possible has advantages: https://news.ycombinator.com/item?id=41409475


I mean, it’s very straightforward generics. There’s just nesting, each of these types has one or two parameters and that’s it. I’m not sure there’s a simpler way of communicating the same thing.

But I also think that I wouldn’t design an API that works like this in Rust natively; this is adapting a signature from C rather than doing the API you’d want if you were creating something from whole cloth.

I also believe that static types are very different based on the language; I’d rather write Ruby than Java, but I’d rather write Rust than Ruby. More advanced type systems tend to actually pull their weight, whereas simpler ones don’t give enough juice for the squeeze, IMHO.


IMHO there's a pretty wide range between static weak typing and static strong typing even within a single language.

Going too far into either extreme has more downsides than upsides - yes, strongly typed code will be more correct because the types might catch usage 'logic errors' during compilation, but also harder to maintain when requirements change (because strong types tend to creep into every little corner of a code base - and they are extremely rigid by design making the code which uses them also very rigid).

My goto example where I burned myself in the past is splitting a vec4 into point and vector types.

It totally makes sense from a theoretical design pov (e.g. `point + point` would be illegal, while `point + vector` or `vector + vector` are allowed). But in reality such code is awkward to work with for many little reasons, and the enforced correctness pretty much never catches bugs, it just adds pointless busy work when the code needs to change.


I agree that something like "over-typing" exists. But where it starts is imho very context dependent.

For the lowest level (e.g. an OS) you really want an extremely strict regime. Bugs are "simply not allowed"…

For application level code I think it depends. Nobody would finish anything if the requirement would be to formally verify all your code.

The other thing is: How far you can get before "over-typing" starts is also dependent on the language and how powerful it's type inference system is. Rust is quite weak in this regard.

In a language where the compiler can pass context for you one can have very strong guaranties without making working with the code too awkward. Of course, changing the code will need work. But that's the whole point of type systems: If you change something the compiler will tell you any places where something needs repair. In less strongly typed languages you can just pray that your tests are good enough to cover the changes.

In case of changes a good compiler will even rewrite the trivial cases for you if you ask, and just leave what really needs human oversight. But such automatic rewriting (with the guaranty that nothing went wrong!) works only if you had very strong types in the first place.


I didn't watch any other talks of this conference, but it looks to me as part of the issue here was that the presenter didn't explain basic (ML) types upfront. Maybe they did in other talks earlier, IDK.

If you never seen such types before it may look intimidating. But if you ever seen any ML style language the type of said function looks actually very ordinary. It does not use any really more complex concepts, it's just regular nested generic types. Pretty standard stuff.

In Rust you could also have things like:

  fn foo<'a, T, U, V, W>(a: &'a T, b: U, c: V, d: W)
  where
      T: MyTrait<U> + Debug + 'a,
      U: Debug + Hash,
      V: SomeOtherTrait<V> + Debug + std::marker::Copy,
      W: Box<dyn Fn(V) -> U> + Debug,
  {
      todo!("Just a demo");
  }
That's indeed much more involved, even it doesn't use any deeply nested wrapper types.

But I think nobody would force such complex signatures on API end-users.

(Even there is of course also no magic going on. It's just the quite quite expressive type language in Rust).


Maybe it's because I'm working a lot with V8, but that looks quite a lot like most C++ generics I've been looking at lately. Especially when working with concepts, it's easy to end up 5 or more nested angle brackets in.


Yeah, but is that a good thing? Complex template magic is hardly one of the good parts of modern C++.


Rust generics are much underpowered than C++ templates in order to make them an integral part of type system. Many practical C++ programmers avoid excessive template magics for that reason and also for simplicity, and Rust's restriction is essentially its codification.


But it's not template magic. Not even something close.

It's just nested generic types.

How would you otherwise express all the constrains in a type?


    enum ReceivedINode<T> {
        Existing(ARef<INode<T>)
        New(inode::New<T>)
    }

    fn get(..) -> Result<ReceivedINode<T>>
Calm down with the bikeshedding. It’s just a complex C API and they wanted to convey the semantics concisely.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: