Rust 1.65.0
blog.rust-lang.org
blog.rust-lang.org
This release stabilises Generic Associated Types (GATs), a limited form of Higher Kinded Types. This is only an MVP release, but it already includes enough to be useful, and has been a huge multi-year effort (the RFC for the feature dates to April 2016).
Novice users of Rust probably won't use this feature directly, but it gives a lot more power to library authors to write more flexible APIs while still keeping all of Rust's strict correctness guardrails (i.e. exposing a safe API). It will also form the basis of the upcoming "async functions in traits" feature.
This is probably the most significant Rust release in the last couple of years.
From that thread, a link https://www.fpcomplete.com/blog/monads-gats-nightly-rust/ to a comparison of GAT's w/ HKTs as seen in high-level functional languages. Note that the version of GAT's provided here is a MVP and might be unable to express some of these patterns.
I also wonder whether Linux kernel community would adapt these concepts or reject them for the sake of KISS.
And coincidentally, the times I've wanted to reach for GATs have been when dealing with Futures (specifically dealing with lifetimes of Futures produced by Fns; basically what gates async functions in traits). I'm excited to be able to finally use them in my APIs!
> It will also form the basis of the upcoming "async functions in traits" feature.
I can stop using https://crates.io/crates/async-trait !
1. The ability to use generics in associated types
2. The ability to use `impl Trait` in associated types
GATs provide the first, but the latter is still being worked on. The good news is that the implementation has matured greatly this year and appears to be in a state where stabilization could be considered. AFAICT this work is being done in the broader context of async methods, tracked here: https://github.com/rust-lang/rust/issues/91611
let foo = match do_something() {
Ok(f) => f,
Err(e) => {
// Do some error handling, maybe log a warning, maybe skip this item and continue...
}
};
This small change makes that so much more clear, removes the silly first row and removes a level of indentation. let Ok(foo) = do_something() else {
// Do some error handling, maybe log a warning, maybe skip this item and continue...
};It's still convenient, but in really limited circumstances, AFAICT. This week I'll be going through the various Rust projects I maintain at work, seeing if there's useful places for let-else.
If you want help from automation, you can wait a few days until the manual_let_else clippy lint arrives on nightly. It's going to be one of the pedantic lints, and recognizes some obvious places where let else would make sense (not all but many). It should arrive in one week-ish, depending on when the next clippy update is in nightly.
if foo.is_none() {
return;
}
let foo = foo.unwrap()
Now I can do simply let Some(foo_unwrapped) = foo else {
return;
}
which is prettier than the `if let (...)` to just unwrap it IMO. let foo_unwrapped = match foo {
Some(foo_unwrapped) => foo_unwrapped,
None => return,
};
Not as pretty, but you don't have to unwrap. let x_result = something;
let Ok(x) = x_result else {
...
}
But I expect that it will be used mostly with Option, as in "else continue" or "else break". if let Error(e) = my_func() {
//... Do something with e
}Edited to add: I don't mean to imply that you should always use `if let` for those cases - that may be but I reserve judgement on it.
I'm very impressed with how much good stuff is in this release. The Rust team is killing it.
It is nice for collapsing pyramids of doom that come from normal precondition checking. You can put multiple comparisons or variable assignments into a a single guard as well, separated by commas.
It does not support binding to the error in a Result though, so for these you usually still get more readable code handling these with try/catch, with the corresponding nested code block.
if let Some(x) = maybe_x {
// do stuff with x
} else {
return;
}
now, you'd do: let Some(x) = maybe_x else {
return;
}
// do stuff with x
If you have several instances of this kind of thing, the nesting can be pretty ugly and hard to read, so we'd sometimes do janky things to avoid the nesting, like: let x = if let Some(x) = maybe_x { x } else { return; };
or, if let (Some(x), Some(y)) = (maybe_x, maybe_y) {
// do stuff with x and y with only one layer of nesting
} else {
return;
}In another codebase I had something like
let msg = if let Ok(msg) = ... { msg } else { continue };
becoming let Ok(msg) = ... else { continue }; let foo = if let Ok(f) = do_something() {
f
} else {
// Do some error handling, maybe log a warning, maybe skip this item and continue...
};
It's similar to `let else` but you still need this extra `f` variable that isn't really adding clarity.Break from labeled blocks is a pretty niche thing. I can remember a few cases it would have been useful, but I'm not sure I'll remember the feature exists next time it comes up.
I'm glad that GATs are finally here. I tend to be wary of complex type-system things. I can remember being a new rust programmer around 1.0, dealing with some library with a perhaps-overly-clever API with complex traits and lifetimes and being very frustrated randomly changing code trying to get it to compile. It didn't help that rustc error messages and rustdoc weren't as good and we didn't have any sort of language server back then. Perhaps I'm just not very smart. But even with my focus on simply-typed APIs I've run into several cases where I couldn't do something the obvious way because it needed GATs.
I used to be a lot more involved with Rust, reading a lot of /r/rust and blog posts and issue trackers. I get a warm wistful feeling seeing things like GATs, rust-analyzer, and MIR inlining get stabilized after all these years.
Yeah, I feel like the vast majority of the time this would be better handled by splitting out that block into a function. There are situations where there the number of parameters needed to pass in would get unwieldy, but that is where I start thinking about if all that loose state is intrinsic to the problem, or a sign that I need to refactor my datastructures.
It's a nice idea, and Rust supports "local" fn definitions inside blocks so it would be quite elegant as well.
AIUI, this is a rather specialized feature that only supports diverging code in the "else" block. I assume that the weird syntax was purposely chosen to suggest something like that.
let (x, y) = (1, 2);
...But only for patterns that can never fail. For enums, not all patterns are irrefutable: let Some(x) = Some(42); // error, Rust can't tell that RHS isn't `None`
Note that some languages with pattern matching do allow the above, e.g. OCaml does, and just panics if the match doesn't succeed (so equivalent to `let x = Some(42).unwrap()` in Rust). But Rust favors exhaustive pattern matching, so that wasn't a good fit.The way that Rust solves this is via `if`-style constructs. So you already have `if let`:
if let Some(x) = Some(42) {
// `x` exists in here
}
This produces an inner scope where the contents of the enum are bound. And you can use `else` here as well: if let Some(x) = Some(42) {
// `x` exists in here
} else {
// branch where `x` doesn't exist
}
So the `let else` syntax feels like a natural continuation to me: let Some(x) = Some(42) else {
// branch where `x` doesn't exist
}
// `x` exists here
This reduces the nesting required in several common cases of using enums, which makes code easier to read. (Of course, the interesting thing is that since `x` exists in the outer scope, the compiler requires the inner scope to never reach the outer scope (it must "diverge", by returning or panicking or infinite looping or whatever), which is an extra restriction above what a normal `if else` requires.)Edit: I see now in the first example:
let (Some(count_str), Some(item)) = (it.next(), it.next()) else {
panic!("Can't segment count item pair: '{s}'");
};
Binding the error here would be much verbose, since both must succeed.Edit2: Doh, obviously `None` in this example, not `Err`.
> Libs: Don't generate PartialEq::ne in derive(PartialEq)
Meaning: Smaller generated code leading to faster compile times
> Cargo: Take priority into account within the pending queue.
When not enough CPUs are available (particularly CI), cargo will now prioritize crates with the longest dependency chain. We are wanting to be even smarter about which crates get priority but we still need to figure out what are acceptable heuristics (as we don't want to hard code crate names)
I had to look at the docs to understand what was going on here so I thought I'd share: https://doc.rust-lang.org/std/cmp/trait.PartialEq.html
Apparently the PartialEq trait has two methods - eq and ne (equal and not-equal, == and != respectively). Normally you only implement eq, because ne has a default implementation that's just !self.eq(other). The docs even say:
> Implementations must ensure that eq and ne are consistent with each other
> The default implementation of ne provides this consistency and is almost always sufficient. It should not be overridden without very good reason.
So I'm guessing that `derive(PartialEq)` used to actually generate a fresh ne in each usage (even though presumably it was equivalent to !self.eq(other)), instead of just falling back to the default implementation. I can definitely see that having an impact on compile times.
Do I have that right?
Addendum: Now I'm trying to think of a case where you'd want a custom implementation of ne... I'm not sure I can come up with one
One old example from school was comparing unordered sets of numbers, each backed by a flat array. Summing such an array, contiguous in memory, was very fast: faster than many individual numeric comparisons. So "not equals" checked for equal sums before dropping back to iteration. Of course, that only made sense in situations where more things weren't equal (and didn't share sums) than not, but it helped someone win some performance/vanity extra credit. And I didn't know about vectorized code at the time, so that might make it a wash.
A similar example might be with probablistic checksums of large data. If a checksum answers "maybe the same or definitely not the same" a la a HyperLogLog, comparing checksums is a quick way out for "not equals". Of course, that'd require some uniquely small/untrustworthy checksums, or some uniquely paranoid comparison programmers! That applies similarly to "equals", though.
About the only even vaguely realistic example I can think of is for non-complementary notions of equality and inequality, i.e. an application context in which case it makes sense to have rules like "two MyLists are equal if they contain the same elements, but are not equal if they contain different elements or have different orders". And I call that "realistic" because I've seen code do things like that .... not because it's anything but a terrible idea.
Simpler example: if two lists don't have the same length you know they're unequal without looking at the items. But you could early-terminate eq the same way and actually benefit both eq and ne
As for when it would be useful to override `ne`, very hard to say. The only case I can think of would be to add temporary debug info.
The cause in this case is undeniably worthy, its an awful situation in an often crappy world, but what does it have to do with this release? Where do you start and stop with the issue-raising in technical release news?
You start and stop exactly where you want to - no one owes you or anyone else technical release news without this kind of statement.
What if this was 2003 and the statement said "Due to the newly found weapons of mass destruction in Iraq, we stand in solidarity with the American government for its war against terror"?
I don't think a tech organization should be making such statements in their release notes, which is essentially telling people what should be thought of as right, or wrong, or how to think about a particular issue.
To me its reflective of a potentially dangerous trend that everyone _must_ be an activist and have some kind of alignment.
All I care about is the new and improved logic flow syntax of Rust.
The whole point of activism is to bring issues to an audience that wasn't prepared to hear such statements at a given time: this is why some people get annoyed when they read Notepad++ release notes for the first time!
Being "right" doesn't have anything to do with activism: there's lots of activism within, say, flat earthers, but that doesn't make Earth any less round. Still, in this particular point in time, stating support for women in Iran is probably the mildest form of activism anyone could push for.
Then you stay away from engaging with the underlying community that issues such statements (which might just be a smaller sub-community of the project's wider community).
> All I care about is the new and improved logic flow syntax of Rust.
And you are free to care only about that, use it as a purely technical product and just ignore any political statements (which shouldn't be too hard).
---------
While I don't think every organization intends to have some kind of alignment, they ultimately will all have one, since people do, and organizations are made up of people.
Sometime in the last week, there was an open source federated social network project posted on HN. They have a very political stance when it comes to their developer community, which causes many people to stay away from contributing to the project, to a level where I think it might be the project's long-term demise. While it's never a nice sight to see "wasted effort" like that, given the high level of transparency and contributors willingly joining and staying in the community, that seems like a decent mode of operating.
If we started posting about every terrible wrong in the world, we'd have multiple announcements per day, and they'd have zero impact.
Rust is an international project with participation from all over the world. Whether any particular country is allied with another has zero impact on the discussions that lead to these announcements.
Even if drawing attention was worth anything, I think you are assuming the wrong frequency - the Rust technical release notes may contain few calls to political action, but everyone reading them spends enough time online to be constantly bombarded with them.
Activism is marketing. Unfortunately, marketing is not my field of expertise, so I won't make any hypothesis on its impact.
The demonstrators lined up along the sidewalk of one of the main streets, and were loudly chanting and screaming for a few hours. Others drove by and honked their vehicles' horns.
This was apparently done along stretches of the street with large residential complexes nearby, too.
Based on the bystanders, residents, and other people that my colleagues talked to, they seemed to think that this disruption likely alienated far more people than it attracted to support this particular cause.
Same rhetoric used during the civil rights movement.
Why read further into it? They never said they don’t care about those other things. It is possible for humans to mention a specific cause without also negating all others.
Seriously, what is wrong with the logical reasoning of some people?
Although, I suppose recent political issues that everyone's already talking about, and a project tries to "raise awareness" reeks of a misguided virtue signaling PR stunt than when one talks about long-standing issues. Not saying this is the case here, though, as Rust is hardly something that needs PR.
I'm sure part of the reaction is that Twitter is full of this type of virtue signaling, and people have become allergic to it. I remember the whole "Yeah, this little master->main change will be a tectonic shift. Back pats all around! We did it people; we solved racism!" attitude that Twitter had during the main/master debate lol. Yes, make changes in what is probably the most progressive industry (since we nerds are generally more tolerant/progressive), like that's gonna make a difference in the morally decrepit police force, which the protests were about to begin with.
Anyway... I'm not sure what the word is, but I feel it's a next door neighbour to hypocrisy? And hypocrites annoying everyone.
Personally though, I think it's always good to talk about human rights or mental health issues! I'm not sure a paragraph everyone will skip is an adequate place for that, but it's a nice token effort, and it's somewhat commendable someone cared enough to put it in.
Anyway, this is all a bit incoherent, but I don't care enough to wordsmith the comment, only to share some of my opinions.
Maybe I've done these for selfish reasons because they are things that personally affected my own past/development, and doing them made me feel good and significant. These are all small things, but at least I hope I've made a difference to a small number of people. So, in my books a tweet or a driveby paragraph in release notes doesn't really make a difference. Tell me, how did GitHub help make black people feel more comfortable on the streets of America? How did these release notes help people's rights in Iran?
[1]: Also, to be fair, I don't know who put that paragraph in. Maybe they did do meaningfully contribute (i.e. by setting up VPN networks or something), and maybe I'm being unnecessarily harsh. But in my limited experience, the people who preach the loudest do the least, but also herald of their preaches and coat themselves with efforts of others. Sadly
This is how language works. Usage is meaning. A change in usage is a change in meaning. Language doesn't care about your opinions on the matter, it simply is. The phrase "virtue signaling" now carries with it an accusation of insincerity. If you don't mean to level such an accusation, you're going to need to find a way to communicate what you do mean that isn't encumbered by connotations that have recently become standard.
I agree that solidarity can help. But none of that's going on here... This pathetic attempt at shallow expression isn't going to reach the people who need it (or probably anyone in Iran for that matter, due to the Internet situation there).
> I guess there's no particular obligation for you to care, but would it actually be such a privation for you to stoically weather your indignation about others doing so?
This is degenerating into gaslighting and you trying to put words into my mouth, so I will let the thread rest. I guess I'm happy to agree to disagree on the effectiveness of online virtue signaling[1].
I'm not even judging these people that hard, I think. I'm just describing the response their shallow behaviour elicits in other people. I don't even get such a strong response myself, I just think it's worthless
[1] To avoid confusion I mean "pathetically shallow attempts at trying to make a difference to improve their colleagues perception of their (shallow) morals"; with malicious intent or without, the action is the same. But that concludes me playing online word/semantic lawyer
You have no idea. As I said, I have personal experience of this particular instance of what you stereotype, sans evidence, as 'virtual signalling', having a genuinely heartening effect on a friend whose nephew was killed recently in Iran. This is the trouble with you internet snarlers. You have so little knowledge of physical reality.
That’s your own fault not theirs. Maybe they have some women on their team who are particularly moved by this event? Maybe they have some Iranian expats? Maybe they’re unaware of the extent of other issues? Maybe it doesn’t matter because again , saying one supports a specific cause doesn’t mean they don’t support others.
There is zero hypocrisy here. There are only logical fallacies from people reading too far into things.
There's generally three options when it comes to politics; do nothing, do something, do everything.
The first does not help anyone, and the last; being impossible - is a path back to the first (via burn-out, apathy or simply inefficiency ("scope creep").
So the only real option is to do "something" - and champion a few causes.
Your friend Adam hits Bob. You do nothing. Bob tries to hit back but you grab his arms so he can't. Adam hits Bob. You do nothing. Bob still can't hit back because you are preventing him.
Another possibility:
A Corp in which you are a stockholder is polluting the environment. You do nothing. B Inc pollutes the environment but not as much. You step in to stop them which causes B to lose out on a lot of profit. A Corp outcompetes B Inc. You become rich.
Do I think we are being overly harsh on Iran? No. Should we consider the possibility and try to avoid it? Yes. Should we be suspicious of other people that are biased in what they enforce? Yes.
In the real world, doing something is typically better than doing nothing.
You are walking in the park and see a woman being dragged into a bush to be assaulted. Do you intervene? By doing so are you stopping all assaults against women? No, but invariably people would still say you've done more good by doing something than by doing nothing.
The protestors in Iran haven't hit Bob their government, you're not restraining an equally aggrieved party by backing them against their regime. They're if anything the long term victims of Bob's habitual violence and they're finally seeking to break free, not to hurt Bob, but to be free from being hurt themselves.
A child is starving to death in Sudan. Do you feed them or do nothing? Does feeding them feed all the children? Does feeding none of the children make the world a better place than feeding one? If you were that child, or their parents watching them die in their arms, would you hope people would step away from their completely detached hypotheticals and actually do something to save a life when they could?
Etc etc
Some people do think human solidarity, outside of stereotypically 'political' contexts, is a benefit in itself. I know many Iranians (accident of history - my mother adopted 2 Iranian refugees when I was in my teens). I showed this to one friend, who heard last week a 20 year old nephew of hers had been gunned down by the IRGC. She burst into tears and hugged me and told me how much this kind of expression kept hope alive.
Politics is physical reality, which tends to mug those who prefer to look away. If it mugs you merely via a sentence you prefer not to read, rather than a bullet or hammer, I suspect you can probably cope.
The blog is on github, and this is the commit that added it: https://github.com/rust-lang/blog.rust-lang.org/pull/1043/co...
What is the "leadership chat"? See: https://blog.rust-lang.org/inside-rust/2022/10/06/governance...
Can we see if any of the participants objected to the inclusion of this material, for example?
For full disclosure, this leadership chat was created in the fallout of the mod team resigning (of which I was a member). And I think it's a good thing, because it's getting a bunch of people talking that weren't talking before. And, as the blog post outlined, they're working towards producing an RFC to revamp part of Rust governance. Which... I think is needed.
It's just that... they are also making other decisions now. Whether that's beyond their mandate or not, I don't know. But it's certainly continuing the tradition of the Core Team, which started doing this a couple years ago. And whether they were within their mandate to do such things is also not clear to me.
To me it means that at least one person involved with developing Rust cares about this. I think skipping past it I am not personal interested is a small sacrifice for me to make, considering that I get a new Rust version for free.
I've read
https://rustc-dev-guide.rust-lang.org/mir/index.html
and
https://blog.rust-lang.org/2016/04/19/MIR.html
but both did not enlighten me regarding inlining. Could you ELI5 or point me to relevant documentation?
The reason it's so important is that inlining synergises with other optimizations, its often run before or after other passes like constant propagation. Consider something like:
fn add(x : usize, y : usize) -> usize { x + y }
fn main() {
add(5, 1)
}
Without inlining we can't easily optimize the code of `main`. However, if we inline `add` into main, constant propagation will then directly see `5 + 1` and optimize that into `6`.There is room to improve parallelization, though. Cargo will parallelize invocations of rustc (by crate) and rustc will parallelize backend code generation (functions in a crate are load balanced automatically) but the frontend is still not parallel.
- Generic Associated Types - Let-else - Labelled break - Stable Backtrace API - Split debug info on Linux
Huge thanks to all the contributors and maintainers!
1.00: lang/stdlib stability
1.13: the ? operator
1.15: custom derive
1.31: non-lexical lifetimes, new module system, const fn
1.39: async/await
1.51: const generics mvp
1.64: GATs ← we are here! wahoo!
Anyone successfully transitioned from Node.js/TypeScript/Python background (aka higher level languages) to Rust?
For context: I started out a passionate and evangelical Python "duck typing" proponent. Eventually migrated to Node, as it let me in the browser via JS (i'm fond of web tech). Eventually Go caught my eye due to types (before Typescript really existed) and so i stayed there for a while. Each of these lasted ~5 years.
These days i'm a heavy and solely Rust user.. going on ~3 years now iirc. Both in my work life, and home life.
The more experience i got in my career the more i loved types, so Rust's type system wasn't a shock to me. Yes, it was the most in depth typing i had used, but for me it was a natural transition. I was wanting types to solve problems for me. Rust felt very natural in that sense.
I advocate that Rust can be easy to learn if you focus on your bite sizes. Learn Rust with a keen eye for what looks like big and small bytes, and never exceed your mouth size. In the beginning lifetimes are scary, so avoid them - clone or `Arc` like mad. Be wary of heavy generic usage. Rust has lots of rope to hand yourself with, should you go looking for a noose.
I didn’t have any professional experience in low level languages. I had some hobby/school experience with C and C++, but nothing recent.
Learning rust was not too bad. Took me a couple of tries to get going, but once I got the basics down, expanding from there was pretty straightforward.
At the time I was looking for a job, a solid majority of Rust jobs were in cryptocurrency or adjacent companies. I wasn’t interested in that, but I still found plenty of places to apply to. I got responses from almost all of them, and was able to pick between my top choices. My understanding is that now there are many more non-crypto jobs than there were, although I don’t know to what extent that has been affected by recent economic conditions.
I'm celebrating other things in this release. let else will be great for ergonomics. Stable backtraces in std, split debuginfo on Linux, and const offset_from all address pain points for me. (Backtraces will be more useful though when the provider API lands. Then I think we'll be able to have really great error chains. Right now, there's no obvious way to go from a &dyn std::error::Error to a backtrace without guessing the concrete error type and downcasting, and the same problem applies to other forms of context I'd love to have.)
[1] https://blog.rust-lang.org/2022/10/28/gats-stabilization.htm...
Here's a collection of comments from the same PR from users arguing for stabilization:
"I work on chumsky, a parser combinator crate. I've recently been experimenting with GATs internally as a way to control exactly what code Rust generates. Instead of praying to the LLVM gods that the compiler might optimise things, I use a GAT to project a particular parsing 'strategy' into the implementation of parsers. I've found that I can significantly improve the performance of the library by an order of magnitude, even beating out hand-written parsers, nom, and serde_json (with several caveats) without harming the library's expressivity (and, in fact, improving it). This all happens without the GATs themselves being exposed to library users at all."
https://github.com/rust-lang/rust/pull/96709#issuecomment-11...
"The first time I realized the Iterator trait was insufficient for what I wanted was before Rust 1.0 in 2014 when I wrote one of the first versions of the csv crate. All I wanted to do was write an iterator that lent out a borrow of an internal buffer in order to avoid allocating a new record on each iteration."
https://github.com/rust-lang/rust/pull/96709#issuecomment-11...
"I've been using GATs on nightly for a little less than a year for various experimental proc-macro crates. I can only say that GATs simplify a lot of things for me! I'm not doing things like LendingIterator, my interest is more in "DSL"s. Things like e.g. generate a struct that temporarily stores all the parameters to some function (where some of those parameters will be non-static references). The main concern will often be how much code can I avoid autogenerating i.e. is it possible to write abstractions as libraries over these things. GATs allow me to do that with ease. [...] The one I'm currently working on is unimock. The GAT stuff is only in the gat-mock branch, not released on crates.io yet. That GATified trait is MockFn."
https://github.com/rust-lang/rust/pull/96709#issuecomment-11...
"There is no way to use async traits in an embedded context (no_std) without GAT's or pulling in the alloc crate (to use async-trait). Pulling in alloc for most embedded platforms is not feasible, therefore we are currently locked to nightly for the embedded-hal-async crate."
https://github.com/rust-lang/rust/pull/96709#issuecomment-11...
"Issue #95 on the RustAudio crate for example says, "The first [solution] would be to make PortType generic over a 'a lifetime...however, this has a cascading effect, which would force all downstream users of port types to specify their lifetimes". Pythonesque made a simpler point here, "Without GATs, I ended up having to make an Hkt trait that had to be implemented for every type, define its projections, and then make everything heavily parametric and generic over the various conversions.""
https://github.com/rust-lang/rust/pull/96709#issuecomment-11...
RLS is no longer deprecated, but removed. “Deprecation warning” means “FYI, you shouldn’t be using this, but it’ll still work for now”. This is an obsolescence notification, not a deprecation warning.
I fear we’re losing the fight for the meaning of the word “deprecate”. Yet we have no other word or even phrase to convey what it has historically meant.
Obsolescence is ambiguous. In common usage it means a thing is not maintained and will generally become harder and harder to use due to the lack of maintenance and support, so that at some point it will probably stop working and be unfixable. But especially in the software field it has long also been used to indicate things that actively don’t work any more because they’ve been replaced by something else. In short, “obsolete” already serves as what people are ruining “deprecated” to mean: maybe it works, maybe it doesn’t, but sooner or later it won’t work.
Deprecation, however, must not signify removal. It is purely a form of discouragement of something that still works, at least for now. See https://en.wikipedia.org/wiki/Deprecation.
Error checking; golang's error handling arguably reverses the happy/sad paths of code syntactically, and some users don't like reading things error-first.