Live data from Hacker News

Easy Mode Rust

llogiq.github.io

41–50 of 59 posts

Re: Easy Mode Rust

#41
post #11

Most of this stuff isn't addressing what makes Rust harder than, say, Go or C#. The pattern matching makes it if anything easier, since pattern matched dispatch is way easier to read than a bunch of complicated if/else blocks. Rust is not garbage collected. It's a systems language, so it doesn't ship or run inside a big runtime. Rust's RAII and lifetime system does an admirable job of making most of the headaches and…

100% agree it’s a huge mistake to water down Rust’s `enum` and `match` when teaching Rust.

If anything, these concepts (algebraic data types and pattern matching) are by far the highest “ROI” features to learn of all: they’re super easy to learn, intuitive to read and write, immediately useful, and unlock a level of rich, expressive, inherently robust code not possible in many other languages.

However, I do agree with the author that the borrow checker is the first area of “friction” for most, and so suggesting more liberal use of cloning is a completely fair strategy to make the learning curve less steep for beginners.

Re: Easy Mode Rust

#42
post #28
post #20

Earlier quoted context omitted.

Rustrover (IntelliJ) can do some of this. You can use quick actions to expand a macro inline in the IDE.

As can rust-analyzer, btw. It's very useful sometimes.

How would you interface with rust-analyzer to make it do this? As I understand it rust analyzer acts like an LSP to your IDE, which in turn doesn’t let you just make raw calls to rust analyzer

Re: Easy Mode Rust

#43

Saving this article to point to next time someone asks me yet again "why not rust". I think one could explain the entirety of C in fewer words than this "easy mode" rust.

> I think one could explain the entirety of C in fewer words than this "easy mode" rust.

You really want to skip over the ruleset for undefined behaviour, otherwise "easy mode rust" becomes as long as the just the first chapter of the first book of "a quick intro of undefined behaviour in c".

Re: Easy Mode Rust

#44
post #34

Earlier quoted context omitted.

Rust's language-level support for pattern matching (including exhaustiveness checking) is very nice. Most languages with sum types have this feature. It's hard to imagine one without the other. I haven't used `std::variant` much, but I remember finding it unergonomic. Real-world C++ code uses `std::variant` way less often than Rust/functional code uses sum types. Probably, for that reason UPDATE: also, some advantage…

I agree, the lack of pattern matching is a bummer. You should retry std::variant with the lambda overload trick though. It's kind of okay in terms of syntax. using MyDiscreminatedUnion = std::variant ; MyDiscreminatedUnion var = MyType2{}; std::visit(overload { [](MyType1 t1) {...}, [](MyType2 t2) {...}, [](auto t) {...}, }, var);

You can even do better with something like:

namespace StlHelpers {

template struct overload : Ts... { using Ts::operator()...; };

template overload(Ts...) -> overload;

template auto visit(var_t & variant, Func &&... funcs)

{

    return std::visit(overload{ funcs... }, variant);
}

}

And then

StlHelpers::visit(var,

    [](MyType1 t1) {...},

    [](MyType2 t2) {...},

    [](auto t) {...}
);

Re: Easy Mode Rust

#45
post #38
post #30

Earlier quoted context omitted.

`std::variant` is very awkward to use, and has design compromises because it doesn't have language support. Besides, the ability to write something like `std::variant` as a pure library type first requires you to have a very complex type system, more complex than Rust's, and certainly more complex than a "simple" language such as C or Go would ever consider adopting. C++ is going to have pattern matching "any year no…

Right, I'm not trying to argue that std::variant is better than native language support. By definition, a language construct will always be easier to write and read. All I'm saying is that the current state of std::variant makes it okay enough to use type safe discriminated unions. Visit + overload is not that far away from pattern matching in terms of readability, clang does warn on non exhaustive switch cases, etc.

You did ask more generally:

> Does that really need to be part of the language though, or as long as you can code it, or have it in the standard library, it's fine?

And my answer is, yes. I don't consider `std::variant` a proper replacement, more like a crutch that may even be worse than not having anything at all, because its existence can be used as an argument against introducing language-level sum types in the future.

Re: Easy Mode Rust

#46

This: > add .await after every function call, and then remove it again wherever the compiler complains Should be something Rust (and JS runtimes!) should somehow check/complain. It's just too easy to forget to await on a function returning a Future/Promise. Rust at least will complain if the returning types don't match some expected value and hint a helpful "help: consider `await`ing on the `Future`". Typescript, wel…

> It's just too easy to forget to await on a function returning a Future/Promise. Is there not a way with linting configurations to bump this warning to a "compile time error" (unless marked ignore or something)?

That seems extreme. In both JS and Rust, it’s not unusual to not await the promise/future immediately and just deal with the returned promise directly. Presumably that’s why neither language awaits for you automatically. I would be pretty annoyed if the linter yelled at me every time I did that.

Re: Easy Mode Rust

#47
post #5

Cool cheatsheet. As someone new to Rust, what are the benefits versus Go, C++, and C?

Sibling doesn't really cover the benefits vs. Go, so here's my attempt at a list. * Rust offers memory safety without a GC. You may or may not want a GC. If you don't, then Rust is the better option. * More broadly, Rust has a C++-like focus on providing zero cost abstractions. Go is generally happy to accept a small runtime cost for abstraction. * Rust can generate small WASM targets because it doesn't need to bundl…

I'd like to elaborate on the "fancier type system". It's not all academic. There are obvious practical advantages:

- No more bugs where a value that shouldn't be mutated is accidentally mutated in another place.

- No more bugs with writing to a closed channel or file.

- No more bugs with forgetting to close a file.

- No more null pointer errors at runtime.

- No more runtime reflection errors.

Compared to Go, safe Rust provides all these benefits and more.

Re: Easy Mode Rust

#48
post #32

Earlier quoted context omitted.

Rust can: - Do the things C and C++ can do. - Without the memory corruption issues those languages are infamous for. - With the conveniences you'd expect of any post-internet language. (A library ecosystem that's unified around a standard build system and package manager, an async IO story, UTF-8 strings, etc.)

Rust does not have offsetof (the real deal, not some pointer-based hack in a third-party crate) and using FFI to call C or C++ practically requires bindgen which is not stable, not part of the standard library, and tricky to configure in a portable way. Rust also doesn't have a stable ABI yet though slow progress is being made. If you can write pure Rust in a single library/binary these aren't major issues but as a d…

[deleted]

Re: Easy Mode Rust

#49
post #11

Most of this stuff isn't addressing what makes Rust harder than, say, Go or C#. The pattern matching makes it if anything easier, since pattern matched dispatch is way easier to read than a bunch of complicated if/else blocks. Rust is not garbage collected. It's a systems language, so it doesn't ship or run inside a big runtime. Rust's RAII and lifetime system does an admirable job of making most of the headaches and…

It annoys me to no end that the community has cargo-culted around tokio. Async should also have probably defaulted to single-threaded. Does work-stealing offer that much of a performance advantage over simpler thread per core?

Re: Easy Mode Rust

#50
post #39

for item in items.iter() { if predicate(item) { items.push(modify(item)); } } This is not a good idea in Python either. You are better off creating a new list in which you accumulate all items, both modified and unmodified.

I don’t think I’ve ever seen code of this shape in general, ever. Lists are homogeneous in their semantics, aka items stand for the same semantic thing. This is in contrast to tuples. Both lists and tuples exist in both Rust and Python as well was a bunch of other languages.

After you’re done with the above iteration, what are you going to do with the items list? It’ll contain a mix of semantically different things (all original entries as well as filtered and modified entries).

For example, say items originally is a list of file names. We want to only process image files (the predicate), and want to normalize paths beforehand (modify). Makes sense. But it requires a separate results list. Processed results cannot go at the end of the original list.

(The idiomatic way would be to process outright and not needlessly allocate and populate new lists, but let’s put that aside)

Post reply on HN