mirror of
https://github.com/mfocko/blog.git
synced 2024-11-21 20:43:48 +01:00
blog: add the ‹rust-opinion›
Signed-off-by: Matej Focko <me@mfocko.xyz>
This commit is contained in:
parent
f7c4241a24
commit
06a1e38851
1 changed files with 510 additions and 0 deletions
510
blog/2024-01-28-rust-opinion.md
Normal file
510
blog/2024-01-28-rust-opinion.md
Normal file
|
@ -0,0 +1,510 @@
|
||||||
|
---
|
||||||
|
title: Mixed feelings on Rust
|
||||||
|
description: |
|
||||||
|
Discussing my mixed feelings about the Rust language.
|
||||||
|
date: 2024-01-28
|
||||||
|
authors:
|
||||||
|
- key: mf
|
||||||
|
title: a.k.a. passionate language hater
|
||||||
|
tags:
|
||||||
|
- rust
|
||||||
|
- memory safety
|
||||||
|
- cult
|
||||||
|
- hype
|
||||||
|
hide_table_of_contents: false
|
||||||
|
---
|
||||||
|
|
||||||
|
Rust has become a rather popular language these days. I've managed to get my
|
||||||
|
hands dirty with it during _[Advent of Code]_ ‘22 and partially ‘23. I've also
|
||||||
|
used it for few rounds of _[Codeforces]_ and I have to try very hard to maintain
|
||||||
|
some variety of languages for LeetCode challenges along with the Rust. I'll
|
||||||
|
disclaim up front that I won't be only positive, since this post is a result of
|
||||||
|
multiple discussions about Rust and I stand behind
|
||||||
|
_“All that glitters is not gold”_, so if you can't stand your favorite language
|
||||||
|
being criticized in any way, don't even proceed. :wink:
|
||||||
|
|
||||||
|
<!--truncate-->
|
||||||
|
|
||||||
|
## Memory safety
|
||||||
|
|
||||||
|
I'll start by kicking the biggest benefit of the language, the memory safety.
|
||||||
|
Let's be honest here, majority of the checks rely on the static analysis, cause
|
||||||
|
you can't do anything else during the compile-time, right? Therefore we can
|
||||||
|
basically say that we are relying on the compiler to “solve” all of our issues.
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
|
||||||
|
I'm not doubting the fact that compiler can prevent **a lot** of the memory
|
||||||
|
errors, I'm just saying it's not realistic to cover **everything**.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Compiler
|
||||||
|
|
||||||
|
I guess we can safely[^2] agree on the fact that we 100% rely on the compiler to
|
||||||
|
_have our back_. Is the compiler bug-free? I doubt it. This is not meant in an
|
||||||
|
offensive way to the Rust compiler developers, but we need to be realistic here.
|
||||||
|
It's a compiler, even older and larger projects like _gcc_ or _llvm_ can't avoid
|
||||||
|
bugs to appear.
|
||||||
|
|
||||||
|
When I was trying out Rust for some of the LeetCode challenges I've stumbled
|
||||||
|
upon the following warning:
|
||||||
|
![Example of a compiler bug](https://i.imgur.com/NfPLF6o.png)
|
||||||
|
|
||||||
|
:::danger [Issue](https://github.com/rust-lang/rust/issues/59159)
|
||||||
|
|
||||||
|
The issue here comes from the fact that we have 2 simultaneous references to the
|
||||||
|
same memory (one is mutable and one immutable). If you cannot think of any way
|
||||||
|
this can break, I'll give you a rather simple example from C++ where this could
|
||||||
|
cause an issue.
|
||||||
|
|
||||||
|
Imagine a function that has some complex object and also calls a coroutine which
|
||||||
|
utilizes read-only reference to that object. When the coroutine suspends, the
|
||||||
|
caller can modify the object. This can break the integrity of data read by the
|
||||||
|
coroutine.
|
||||||
|
|
||||||
|
- Yes, this **can** cause a memory error.
|
||||||
|
- Yes, this **hasn't** been handled until someone noticed it.
|
||||||
|
|
||||||
|
Fixing this bug is not backwards compatible, cause you're covering a case that
|
||||||
|
hasn't been covered before.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Enforcing the safety
|
||||||
|
|
||||||
|
One of the ways Rust enforces the safety is by restricting what you can do, like
|
||||||
|
the example above. Aforementioned issue _can_ happen, but **doesn't have to**.
|
||||||
|
Rule of the thumb in the Rust compiler is to _“block”_ anything that can be an
|
||||||
|
issue, static analysis can't do much more, it cannot decide whether it's safe to
|
||||||
|
do it or not.
|
||||||
|
|
||||||
|
Satisfying the Rust compiler is sometimes a brutal pain in the ass, because you
|
||||||
|
cannot do things like you're used to, you need to work around them _somehow_.
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
|
||||||
|
Key difference between Rust and C or C++ lies in the fact that Rust chooses to
|
||||||
|
_ban_ all “potentially offensive” actions, C and C++ _relies_ on **you** to be
|
||||||
|
sure it's safe to do.
|
||||||
|
|
||||||
|
![C++ v. Rust](https://i.imgur.com/0vbkYPp.png)
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Consequences
|
||||||
|
|
||||||
|
Where are we heading with this approach of “if it compiles, it runs” though?
|
||||||
|
In this aspect I have a rather similar opinion as with regards to the ChatGPT
|
||||||
|
and its derivatives.
|
||||||
|
|
||||||
|
If you teach people to 100% depend on the compiler, they will do it, cause it's
|
||||||
|
_easy_. All you need to do is make the compiler _shut up_[^3]. Giving up the
|
||||||
|
_intellectual masturbation_ about the memory safety will make you lose your edge
|
||||||
|
over the time. When we get to the point of everyone being in the mindset
|
||||||
|
mentioned above, who's going to maintain the compiler? This is the place where
|
||||||
|
you **need to** think about the memory safety and furthermore in a much more
|
||||||
|
general way than in your own projects, because it is the thing that everyone
|
||||||
|
_blindly believes in_ in the end.
|
||||||
|
|
||||||
|
I'm not saying that everyone should give up Rust and think about their memory
|
||||||
|
management and potential memory issues. I'm just saying that going the easy way
|
||||||
|
will make people _dull_ and they should think about it anyways, that's how the
|
||||||
|
issue above has been discovered. If everyone walked past and didn't think about
|
||||||
|
it, no one would discover this issue till it bit them hard.
|
||||||
|
|
||||||
|
:::tip Standard library
|
||||||
|
|
||||||
|
Even the standard library is littered with `unsafe` blocks that are prefixed
|
||||||
|
with comments in style:
|
||||||
|
|
||||||
|
```rs
|
||||||
|
// SAFETY: …
|
||||||
|
```
|
||||||
|
|
||||||
|
The fact that the _casual_ Rust dev doesn't have to think much about safety,
|
||||||
|
cause the compiler has their back, doesn't mean that the Rust compiler dev
|
||||||
|
doesn't either.
|
||||||
|
|
||||||
|
I gotta admit that I adopted this concept in other languages (even in Python),
|
||||||
|
cause you can encounter situations where it doesn't have to be clear _why_ you
|
||||||
|
can do _what_ you're doing.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Development & design
|
||||||
|
|
||||||
|
Development of Rust is… very fast. One positive is that they're trying to be as
|
||||||
|
backward compatible as possible at least by verifying against all the published
|
||||||
|
crates in the process. Of course, you cannot be backward compatible about fixing
|
||||||
|
the bugs that have been found, but such is life.
|
||||||
|
|
||||||
|
### Fast development cycle
|
||||||
|
|
||||||
|
One of the negatives of the fast development cycle is the fact that they're
|
||||||
|
using the latest features already in the next release of the Rust. Yes, it is
|
||||||
|
something that you can use for verifying and testing your own changes, but at
|
||||||
|
the same time it places a requirement of the latest release to compile the next
|
||||||
|
one.
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
|
||||||
|
If you check `gcc` for example, they have a requirement of minimal version of
|
||||||
|
compiler that you need for the build. Though gcc's requirement is not so _needy_
|
||||||
|
as the Rust one.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
One of the other negatives is the introduction of bugs. If you're pushing
|
||||||
|
changes, somewhat mindlessly, at such a fast pace, it is inevitable to introduce
|
||||||
|
a bunch bugs in the process. Checking the GitHub issue tracker with
|
||||||
|
|
||||||
|
```
|
||||||
|
is:issue is:open label:C-bug label:T-compiler
|
||||||
|
```
|
||||||
|
|
||||||
|
yields **2,224** open issues at the time of writing this post.
|
||||||
|
|
||||||
|
### RFCs
|
||||||
|
|
||||||
|
You can find **a lot** of RFCs for the Rust. Some of them are more questionable
|
||||||
|
than the others. Fun thing is that a lot of them make it to the nightly builds,
|
||||||
|
so they can be tested and polished off. Even the questionable ones… I'll leave
|
||||||
|
few examples for a better understanding.
|
||||||
|
|
||||||
|
One of such features is the `do yeet` expression:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#![feature(yeet_expr)]
|
||||||
|
|
||||||
|
fn foo() -> Result<String, i32> {
|
||||||
|
do yeet 4;
|
||||||
|
}
|
||||||
|
assert_eq!(foo(), Err(4));
|
||||||
|
|
||||||
|
fn bar() -> Option<String> {
|
||||||
|
do yeet;
|
||||||
|
}
|
||||||
|
assert_eq!(bar(), None);
|
||||||
|
```
|
||||||
|
|
||||||
|
It allows you to “yeet” the errors out of the functions that return `Result` or
|
||||||
|
`Option`.
|
||||||
|
|
||||||
|
[One](https://github.com/rust-lang/rfcs/pull/3503) of the more recent ones is
|
||||||
|
the ability to include Cargo manifests into the sources, so you can do something
|
||||||
|
like:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#!/usr/bin/env cargo
|
||||||
|
---
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4.2", features = ["derive"] }
|
||||||
|
---
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[clap(version)]
|
||||||
|
struct Args {
|
||||||
|
#[clap(short, long, help = "Path to config")]
|
||||||
|
config: Option<std::path::PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = Args::parse();
|
||||||
|
println!("{:?}", args);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
I would say you can get almost anything into the language…
|
||||||
|
|
||||||
|
## Community and hype train
|
||||||
|
|
||||||
|
Rust community is a rather unique thing. A lot of people will hate me for this,
|
||||||
|
but I can't help, but to compare them to _militant vegans_. I'll go through some
|
||||||
|
of the things related to it, so I can support my opinion at least.
|
||||||
|
|
||||||
|
_Rust is the best language._ It is not. There is no best language, each has its
|
||||||
|
own positives and negatives, you need to choose the language that's **the most**
|
||||||
|
**suitable for your use case**. There are areas where Rust excels, though I have
|
||||||
|
to admit it's very close to being a universal hammer regardless of how suitable
|
||||||
|
it is. There is a very steep learning curve to it, beginnings in Rust are very
|
||||||
|
painful.
|
||||||
|
|
||||||
|
_Rewrite everything in Rust._ Just no. There are multiple feedbacks on doing
|
||||||
|
rewrites, it is very common to fix _N_ bugs with a rewrite while introducing
|
||||||
|
_N + 1_ other bugs in the process. It doesn't solve anything unless there are
|
||||||
|
some strong reasons to go with it. Majority of such suggested rewrites don't
|
||||||
|
have those reasons though.
|
||||||
|
|
||||||
|
_Language ‹x› is bad, though in Rust…_ Cherry-picking one specific pain point of
|
||||||
|
one language and reflecting how it is better in other language can go both ways.
|
||||||
|
For example it is rather easy to pick the limitations imposed by Rust compiler
|
||||||
|
and show how it's possible in other languages :man_shrugging:
|
||||||
|
|
||||||
|
I don't mind any of those opinions, you're free to have them, as long as you
|
||||||
|
don't rub them in my face which is not the usual case… This experience makes it
|
||||||
|
just worse for me, part of this post may be also influenced by this fact.
|
||||||
|
|
||||||
|
### Rust in Linux
|
||||||
|
|
||||||
|
:::caution
|
||||||
|
|
||||||
|
As someone who has seen the way Linux kernel is built in the RHEL ecosystem, how
|
||||||
|
complex the whole thing is and how much resources you need to proceed, I have
|
||||||
|
very strong opinions on this topic.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
It took years of work to even “incorporate” Rust into the Linux codebase, just
|
||||||
|
to get the “Hello World!”. I don't have anything against the idea of writing
|
||||||
|
drivers in the Rust, I bet it can catch a lot of common mistakes, but still
|
||||||
|
introducing Rust to the kernel is another step to enlarge the monster.
|
||||||
|
|
||||||
|
I have to admit though that the _Apple GPU_ driver for Linux written in Rust is
|
||||||
|
quite impressive. Apart from that there are not so many benefits, yet…
|
||||||
|
|
||||||
|
## Packaging
|
||||||
|
|
||||||
|
I'll divide the packaging into the packaging of the language itself and the
|
||||||
|
programs written in Rust.
|
||||||
|
|
||||||
|
Let's start with the `cargo` itself though. Package managers of the languages
|
||||||
|
usually get a lot of hate (you can take `npm` or `pip` as examples[^1]). If
|
||||||
|
you've ever tried out Rust, I bet you already know where I'm going with this.
|
||||||
|
Yes, I mean the compilation times, or even Cargo downloading _whole_ index of
|
||||||
|
crates just so you can update that one dependency (and 3 millions of indirect
|
||||||
|
deps). When I was doing AoC ‘22 in Rust, I've set up `sccache` right away on the
|
||||||
|
first day.
|
||||||
|
|
||||||
|
Let's move to the packaging of the Rust itself, it's tedious. Rust has a very
|
||||||
|
fast development cycle and doesn't even try to make the builds backward
|
||||||
|
compatible. If there is a new release of Rust, there is a very high chance that
|
||||||
|
you cannot build that release with anything other than **the latest** Rust
|
||||||
|
release. If you have ever touched the packaging, you know that this is something
|
||||||
|
that can cause a lot of problems, cause you need the second-to-latest version to
|
||||||
|
compile the latest version, don't forget that this applies inductively… People
|
||||||
|
running _Gentoo_ could tell you a lot about this.
|
||||||
|
|
||||||
|
:::info
|
||||||
|
|
||||||
|
Compiling the compilers takes usually more time than compiling the kernel
|
||||||
|
itself…
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
I cannot speak about packaging of Rust programs in other than RHEL-based
|
||||||
|
distros, though I can speak about RHEL ecosystem. Fedora packaging guidelines
|
||||||
|
specify that you need to build each and every dependency of the program
|
||||||
|
separately. I wanted to try out _AlmaLinux_ and install Alacritty there and I
|
||||||
|
failed miserably. The solution that worked, consisted of ignoring the packaging
|
||||||
|
guidelines, running `cargo build` and consuming the binaries afterwards.
|
||||||
|
Dependencies of the Rust programs are of a similar nature as JS dependencies.
|
||||||
|
|
||||||
|
> I'm tipping my fedora[^2] in the general direction of the maintainers of Rust
|
||||||
|
> packages in RHEL ecosystem. I wouldn't be able to do this without losing my
|
||||||
|
> sanity.
|
||||||
|
|
||||||
|
## Likes
|
||||||
|
|
||||||
|
If you've come all the way here and you're a Rustacean, I believe I've managed
|
||||||
|
to get your blood boiling, so it's time to finish this off by stuff I like about
|
||||||
|
Rust. I doubt I will be able to cover everything, but I can try at least. You
|
||||||
|
have to admit it's much easier to remember the bad stuff as opposed to the good.
|
||||||
|
:wink:
|
||||||
|
|
||||||
|
### Workflow and toolchain
|
||||||
|
|
||||||
|
I prefered using Rust for the _Advent of Code_ and _Codeforces_ as it provides
|
||||||
|
a rather easy way to test the solutions before running them with the challenge
|
||||||
|
input (or test runner). I can give an example from the _Advent of Code_:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use aoc_2023::*;
|
||||||
|
|
||||||
|
type Output1 = i32;
|
||||||
|
type Output2 = Output1;
|
||||||
|
|
||||||
|
struct DayXX {}
|
||||||
|
impl Solution<Output1, Output2> for DayXX {
|
||||||
|
fn new<P: AsRef<Path>>(pathname: P) -> Self {
|
||||||
|
let lines: Vec<String> = file_to_lines(pathname);
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn part_1(&mut self) -> Output1 {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn part_2(&mut self) -> Output2 {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
DayXX::main()
|
||||||
|
}
|
||||||
|
|
||||||
|
test_sample!(day_XX, DayXX, 42, 69);
|
||||||
|
```
|
||||||
|
|
||||||
|
This was the skeleton I've used and the macro at the end is my own creation that
|
||||||
|
expands to:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[cfg(test)]
|
||||||
|
mod day_XX {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn part_1() {
|
||||||
|
let path = DayXX::get_sample(1);
|
||||||
|
let mut day = DayXX::new(path);
|
||||||
|
assert_eq!(day.part_1(), 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn part_2() {
|
||||||
|
let path = DayXX::get_sample(2);
|
||||||
|
let mut day = DayXX::new(path);
|
||||||
|
assert_eq!(day.part_2(), 69);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When you're solving the problem, all you need to do is switch between
|
||||||
|
`cargo test` and `cargo run` to check the answer to either sample or the
|
||||||
|
challenge input itself.
|
||||||
|
|
||||||
|
Introduce [bacon] and it gets even better. Bacon is a CLI tool that wraps around
|
||||||
|
the `cargo` and allows you to check, run, lint or run tests on each file save.
|
||||||
|
It's a very pleasant thing for a so-called _compiler-assisted_ development.
|
||||||
|
|
||||||
|
Speaking of linting from within the bacon, you cannot leave out the [clippy].
|
||||||
|
Not only it can whip your ass because of errors, but it can also produce a lot
|
||||||
|
of helpful suggestions, for example passing slices by borrow instead of
|
||||||
|
borrowing the `Vec` itself when you don't need it.
|
||||||
|
|
||||||
|
### Standard library
|
||||||
|
|
||||||
|
There's **a lot** included in the standard library. It almost feels like you
|
||||||
|
have all you need[^4]. I like placeholders (like `todo!()`, `unreachable!()`,
|
||||||
|
`unimplemented!()`) to the extent of
|
||||||
|
[implementing](/cpp/exceptions-and-raii/placeholders) them as exceptions in C++.
|
||||||
|
|
||||||
|
You can find almost anything. Though you can also hit some very weird issues
|
||||||
|
with some of the nuances of the type system.
|
||||||
|
|
||||||
|
### `unsafe`
|
||||||
|
|
||||||
|
This might be something that people like to avoid as much as possible. However I
|
||||||
|
think that forming a habit of commenting posibly unsafe operations in **any**
|
||||||
|
language is a good habit, as I've mentioned above. You should be able to argue
|
||||||
|
why you can do something safely, even if the compiler is not kicking your ass
|
||||||
|
because of it.
|
||||||
|
|
||||||
|
Excerpt of such comment from work:
|
||||||
|
|
||||||
|
```py
|
||||||
|
# SAFETY: Taking first package instead of specific package should be
|
||||||
|
# safe, since we have put a requirement on »one« ‹upstream_project_url›
|
||||||
|
# per Packit config, i.e. even if we're dealing with a monorepo, there
|
||||||
|
# is only »one« upstream. If there is one upstream, there is only one
|
||||||
|
# set of GPG keys that can be allowed.
|
||||||
|
return self.downstream_config.packages[
|
||||||
|
self.downstream_config._first_package
|
||||||
|
].allowed_gpg_keys
|
||||||
|
```
|
||||||
|
|
||||||
|
### Traits
|
||||||
|
|
||||||
|
One of the other things I like are the traits. They are more restrictive than
|
||||||
|
templates or concepts in C++, but they're doing their job pretty good. If you
|
||||||
|
are building library and require multiple traits to be satisfied it means a lot
|
||||||
|
of copy-paste, but that's soon to be fixed by the [trait aliases].
|
||||||
|
|
||||||
|
:::tip Comparing to other languages
|
||||||
|
|
||||||
|
On Wikipedia I've seen trait being defined as a more restrictive type class as
|
||||||
|
you may know it from the Haskell for example. C++ isn't behind either with its
|
||||||
|
_constraints and concepts_. I would say that we can order them in the following
|
||||||
|
order based on the complexity they can express:
|
||||||
|
|
||||||
|
```
|
||||||
|
Rust's trait < Haskell's type class < C++'s concept
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
You can also hit some issues, like me when trying to support conversions between
|
||||||
|
underlying numeric types of a 2D vectors or support for using an operator from
|
||||||
|
both sides (I couldn't get `c * u` to work in the same way as `u * c` because
|
||||||
|
the first one requires you to implement the trait of a built-in type).
|
||||||
|
|
||||||
|
:::warning Implementation
|
||||||
|
|
||||||
|
Implementing traits lies in
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl SomeTrait for SomeStruct {
|
||||||
|
// implementation goes here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
One of the things I **would love to** see is being able to define the helper
|
||||||
|
functions within the same block. As of now, the only things allowed are the ones
|
||||||
|
that are required by the trait, which in the end results in a randomly lying
|
||||||
|
functions around (or in a implementation of the structure itself). I don't like
|
||||||
|
this mess at all…
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Influence of functional paradigm
|
||||||
|
|
||||||
|
You can see a big influence of the functional paradigm. Not only in iterators,
|
||||||
|
but also in the other parts of the language. For example I prefer `Option<T>` or
|
||||||
|
`Result<T, E>` to `null`s and exceptions. Pattern matching together with
|
||||||
|
compiler both enforces handling of the errors and rather user-friendly way of
|
||||||
|
doing it.
|
||||||
|
|
||||||
|
Not to mention `.and_then()` and such. However spending most of the time with
|
||||||
|
the AoC you get pretty annoyed of the repetitive `.unwrap()` during parsing,
|
||||||
|
since you are guaranteed correct input.
|
||||||
|
|
||||||
|
### Macros
|
||||||
|
|
||||||
|
Macros are a very strong pro of the Rust. And no, we're not going to talk about
|
||||||
|
the procedural macros…
|
||||||
|
|
||||||
|
As I've shown above I've managed to “tame” a lot of copy-paste in the tests for
|
||||||
|
the AoC by utilizing a macro that generated a very basic template for the tests.
|
||||||
|
|
||||||
|
As I have mentioned the traits above, I cannot forget to give props to `derive`
|
||||||
|
macro that allows you to “deduce” the default implementation. It is very helpful
|
||||||
|
for a tedious tasks like implementing `Debug` (for printing out the structures)
|
||||||
|
or comparisons, though with the comparisons you need to be careful about the
|
||||||
|
default implementation, it has already bitten me once or twice.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Overall there are many things about the Rust I like and would love to see them
|
||||||
|
implemented in other languages. However there are also many things I don't like.
|
||||||
|
Nothing is **exclusively** black and white.
|
||||||
|
|
||||||
|
[advent of code]: https://adventofcode.com
|
||||||
|
[bacon]: https://dystroy.org/bacon/
|
||||||
|
[clippy]: https://github.com/rust-lang/rust-clippy
|
||||||
|
[codeforces]: https://codeforces.com
|
||||||
|
[trait aliases]: https://github.com/rust-lang/rfcs/blob/master/text/1733-trait-alias.md
|
||||||
|
|
||||||
|
[^1]:
|
||||||
|
not to even mention multiple different packaging standards Python has, which
|
||||||
|
is borderline https://xkcd.com/927/
|
||||||
|
|
||||||
|
[^2]: pun intended
|
||||||
|
[^3]: It's not that easy with the Rust compiler, but OK…
|
||||||
|
[^4]:
|
||||||
|
unlike Python where there's whole universe in the language itself, yet there
|
||||||
|
are essential things not present…
|
Loading…
Reference in a new issue