HNHacker News
TopNewBestAskShowJobs

estebank

6,197 karma · joined June 6, 2012

hn@kuber.com.ar
submissionscomments
estebank··on Parsers don't have to be complicated
>> Built-in line and column tracking. Any movement across a newline updates the line number, including a backwards seek. getLine and getColumn are always available and both are one-based, which makes decent error messages nearly free.

> That doesn't sound like much, but having hand-written plenty of recursive descent parsers, it's most of what you need for good error messages.

In my experience having access to the appropriate place where the parser failed is necessary but wholly insufficient for good diagnostics.

estebank··on Parsers don't have to be complicated
I will provide some context from having done a lot of that work.

The Rust grammar is actually quite regular, that's why we have things like the turbofish for type parameters (`binding.method::<Type>()`): it makes the grammar unambiguous (a naïve parser would with a complicated grammar that accepts chained comparisons would have to deal with differentiating between `binding.method < value > ()` and `binding.method<Type>()`). But that doesn't mean the rustc parser doesn't do the work of supporting some the more complex grammar in order to provide better diagnostics. I like to say that rustc actually knows about meta-Rust, a daughter language that goes crazier in its features. I also joke that rustc isn't done until you can paste code from another language and following the suggestions you end up with valid Rust code without loss of the user's intent.

Part of the problem is that the places where incorrect code can fail is in more places than the parser. The chained comparisons example is one that is easy for Rust (as it doesn't support them), so the parser itself can produce a "missing turbofish" suggestion with high certainty, but for truly ambiguous expressions, the errors will happen later, during name resolution ("expected a value and found a type") or when checking the number of arguments. A production compiler needs to account for not only the original error, but also silence every knock-down error too. The simplest strategies are to just stop if at the end of a given stage there are errors (which leads to the "wave of errors" experience of fixing the "last" error leading to a ton of new ones) or fully replacing entire blocks of code that had a parse error with an AST node that acts as a tombstone marking that that later stages need to ignore it. The first option leads to a bad experience, and the latter is insufficient. A recent example of looking at this is https://github.com/rust-lang/rust/pull/159689, where `Arc::new(RwLock::new(HashMap<i32, i64>::default()));` currently produces

  error[E0423]: expected value, found struct `HashMap`
    --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:11:34
     |
  LL |     let _ = Arc::new(RwLock::new(HashMap<i32, i64>::default()));
     |                                  ^^^^^^^
     |
    --> $SRC_DIR/std/src/collections/hash/map.rs:LL:COL
    ::: $SRC_DIR/std/src/collections/hash/map.rs:LL:COL
     |
     = note: `HashMap` defined here
  
  error[E0423]: expected value, found builtin type `i32`
    --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:11:42
     |
  LL |     let _ = Arc::new(RwLock::new(HashMap<i32, i64>::default()));
     |                                          ^^^ not a value
  
  error[E0423]: expected value, found builtin type `i64`
    --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:11:47
     |
  LL |     let _ = Arc::new(RwLock::new(HashMap<i32, i64>::default()));
     |                                               ^^^ not a value
  
  error[E0425]: cannot find external crate `default` in the crate root
    --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:11:53
     |
  LL |     let _ = Arc::new(RwLock::new(HashMap<i32, i64>::default()));
     |                                                     ^^^^^^^ not found in the crate root
  
  error[E0061]: this function takes 1 argument but 2 arguments were supplied
    --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:11:22
     |
  LL |     let _ = Arc::new(RwLock::new(HashMap<i32, i64>::default()));
     |                      ^^^^^^^^^^^              --------------- unexpected argument #2 of type `bool`
     |
  note: associated function defined here
    --> $SRC_DIR/std/src/sync/poison/rwlock.rs:LL:COL
  help: remove the extra argument
     |
  LL -     let _ = Arc::new(RwLock::new(HashMap<i32, i64>::default()));
  LL +     let _ = Arc::new(RwLock::new(HashMap<i32));
     |
This is because the expression is syntactically correct as

  RwLock::new( HashMap < i32, i64 > ::default() );
  ^^^^^^^^^^^^ ------- - ---^ --- - ----------- ^
  |            |       | |  | |   | |
  |            |       | |  | |   | a function call to `default` in the crate root
  |            |       | |  | |   a more than binop
  |            |       | |  | a value to be compared
  |            |       | |  the separator of the second argument to `RwLock::new()`
  |            |       | a value to be compared
  |            |       a less than binop
  |            a value to be compared
  an associated function call
 
but after that PR it would only be the following, even though the parser hasn't changed:

  error: can't compare two types
    --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:24:41
     |
  LL |     let _ = Arc::new(RwLock::new(HashMap<i32, i64>::default()));
     |                                         ^        ^ these are parsed as "less than" and "greater than"
     |
  help: you likely intended to write type `HashMap` with type parameters, but type parameters in expression contexts require the use of the "turbofish" `::<>`
     |
  LL |     let _ = Arc::new(RwLock::new(HashMap::<i32, i64>::default()));
     |                                         ++
I think that there's a lot of work needed in the parser itself to produce good diagnostics. There are other strategies, like performing multiple parses at a given point when you've reached a known bad state (you've seen a flag-post that shouldn't be there, but that is a signal for a handful of other known cases), or fully consuming the rest of a block when an unrecoverable parse occurred (we're half-way through parsing function arguments, but failed? consume the rest of the statement or of the parent block, accounting for sub-scopes). The latter can cause the rest of the file to be consumed, but that's an edge-case that in practice is much better than a deluge of irrelevant errors.

Another added complexity is how some easy-to-hit errors occur during lexing, which means the compiler has barely any information about the user's code. Mismatched braces/parens is one of those. rustc tries to provide context by keeping a queue of seen open delimiters to point at, and explicitly checking for their indentation level as a heuristic to detect where the user's intent diverged from the code, but that's overly reliant on the code being sanely formatted (thanks to rustfmt-on-save, that's a good bet for many users). For an example of the things rustc can do even in the lexer, you can look at https://github.com/rust-lang/rust/pull/160592.

estebank··on JEP 401: Value Objects (Preview) merged to OpenJDK master
It's a project years in the making.
estebank··on Memory safety absolutists
The thing that has changed is that there are spaces where security is being taken more seriously, and C's memory unsafety became a deal breaker there. I do think that providing a mechanism to run existing C software that isn't performance sensitive in a way that mitigates its limitations is very worthwhile.

I think the "static analysis" and the "runtime checks" approaches are complementary, not in opposition, making any noise around having to choose one or the other moot.

estebank··on Memory safety absolutists
Could you point me at the members of the Rust project doing so? I'd want to have a word with them (I'm a member of t-compiler).
estebank··on How Our Rust-to-Zig Rewrite Is Going
I don't think using an example that includes licensing, plenty of regulations governing drivers, the cars themselves and the built environment, enforcement, and plenty of research on the impact of passive infrastructure to make things safer (bollards, daylighting, raised pedestrian intersections, curbs, traffic lights, etc.) makes the case you seem to be trying to make.
estebank··on How Our Rust-to-Zig Rewrite Is Going
There's a material difference on one taking risks for oneself and one taking risks where the brunt of the consequences fall on others.
estebank··on How Our Rust-to-Zig Rewrite Is Going
> that just lost his most popular open source project

As they state in the article, they started the migration a year and a half ago, something that happened a few weeks back would never come into the decision making process.

estebank··on SQLite should have (Rust-style) editions
> Are there any drawbacks to Rust editions?

That they can't (yet) be used to influence name resolution, so that they can't be leveraged for std API evolution. Work is being done on that front.

Older documentation that people find on the internet might be out of date and claim things that don't work but do on later editions, or vice-versa when trying out something that exists in a new edition in an older one. The mitigation for this is that if something is accepted in one edition but not another it must be taken into account in diagnostics so that the divergence is explained.

> Does it make the compiler and other tools larger and more complex for each new edition?

With editions 2015, 2018, 2021 and 2024, there are ~120 branches in the compiler for changes in behavior, including for changing diagnostics to be more specific. For comparison, just rendering a type error for if else having different types in its arms checks for 4 different conditions. 120 checks is a drop in the bucket compared to the number of things the compiler already has to check for, and the growth rate is very manageable accruing in the double digits over the course of 4 years.

> Are the migration tools always fully automatic, quick to execute, and flawless? Is any manual work, or expertise, required for upgrading in the worst case?

> Fully automatic

For most code, it is. We spend the time creating migration lints that can change your code as part of your upgrade, and we test those using crater to detect the cases that people are using in public libraries. It is conceivable that a project in a private repo is using a pattern that wasn't detected through that, so an appropriate structured suggestion isn't emitted, but I haven't heard of such a situation.

> quick to execute

it is as fast as cargo check

> flawless

If the compiler produces a structured suggestion marked as machine applicable, and I believe all edition suggestions are, our level of confidence on them being the right thing to do to get the users' code to compile with the intended behavior is high. Bugs can happen, but don't recall seeing any "incorrect suggestion" in edition lints.

> If I read a blog about Rust, and the blog forgot to mention which edition it uses for its code examples, can that cause any problems? Should I just skip the blog if it is old?

Differences between editions are actually quite small. You should be able to pick up The Book from 2015 and still learn the language. There are just things that The Book says don't work that now do, or features that were introduced that it doesn't talk about (both impl Trait and + use<'_> were introduced years later).

> If I see an interesting repository that I would like to learn from, but it was written in an old edition that I do not want to learn the rules for, will I have to upgrade the codebase myself before learning from it, or should I just skip it?

Just use the old edition or upgrade, either thing will just work. You seem to think that the behavior divergences are bigger than they are, and anything that does diverge should be emitting a diagnostic telling you what code to write instead. These are things like "in a previous edition you had to borrow here, in the next edition you don't".

> Are there any Rust projects that stay in an old edition for whatever reasons? How large a proportion of Rust projects that are still downloaded and used, are not on edition 2024? As an example, why are these popular projects still on Rust edition 2021?

Yes, there are crates that stay in older editions so that they can be used in projects that are frozen in time, like those that are shipped in some distros. (Edit: "frozen in time" as in "they won't update their toolchain", an earlier edition will always work on later ones due to backwards compat guarantees, the other way around of course can't.) To get a sense for it, serde which is a dependency that's almost inescapable is on 2021 edition and targets Rust 1.56. If a project decides that the impact on the ecosystem is bigger than the benefit of using newer language features, then it makes perfect sense to remain in an older edition.

https://lib.rs/stats sadly doesn't include statistics about the set edition, but it does have the graph at the bottom showing you rustc versions that the ecosystem supports, both for all crates and for the most recently updated. For reference, looking at releases.rs to refresh my memory, 1.31 is the introduction of edition 2018, 1.56 2021 and 1.85 2024. Those corresponds with the jumps in the chart. Note that looking at the chart it only tells you the likelihood of a random crate working on an older toolchain.

> How much work is it to upgrade someone else's codebase to a newer edition?

Not any more than updating your own codebase (and if you're upgrading someone else's it is because you've taken ownership of that code), which is to say, not much.

> Does upgrading a large project require intimate knowledge of that project in the worst case?

I can't think of any feature that would require that.

> How do Rust editions and macros interact?

The compiler has the concept of `Span`s, which is the byte offset pointing at a piece of your code. Every node in the AST has a `Span`, and is subsequently used in every later stage. It's what is used to render the diagnostics. But they also carry "context" information: whether a token comes from a macro expansion. And they also carry edition information. This let's you have a macro defined in one edition and use it in another, and the behavior will be the correct one, regardless of how you mix them.

> Does definition of macros or usage of macros make upgrading a Rust edition harder? If yes, how much harder in the worst case? Is upgrading always automatic?

The only situation I can think of is if there is a change in the parsing of macro arguments. For example, if Rust edition 20XX started parsing `A | B` as an anonymous enum type, then the macro call for `foo!(A | B)` would likely be parsed differently between editions <20XX and edition 20XX. That would be the kind of work you'd have to do. This has happened before in edition 2024, with expressions: https://doc.rust-lang.org/reference/macros-by-example.html#m.... In 2024 `expr` started not matching on `_` and `const {..}`. What was done then was introducing an `expr_2021` matcher with the prior behavior. When you update your edition to 2024 and apply the suggestions, your macro definitions get updated to use `expr_2021` instead of `expr`.

From your other comment

> Reading between the lines, it is as if you consider there to be large problems with Rust editions, but you do not wish to make Rust, Rust editions, nor the programming language concept of editions, look bad.

Reading your lines, it is as if you already had the conclusion that editions are bad, and are looking for a confession.

> Does upgrading a large project require intimate knowledge of that project in the worst case? How much work is it in the worst case?

I can't think of a situation where intimate knowledge of the project is needed.

> How do Rust editions and macros interact? Does definition of macros or usage of macros make upgrading a Rust edition harder? If yes, how much harder in the worst case?

Answered earlier.

estebank··on Theo de Raadt: "You've been smoking something mind altering" (2007)
I think that everyone has the power to be wrong, but to be very wrong with convincing arguments, you must be smart.

A smart person can come up with post-hoc rationalizations that hold up under some scrutiny, to the point it is very hard to convince them otherwise. Add to that people who became famous or successful on the back of "being right" on some subject matter, getting used to "being right even in the face of overwhelming push back", and you have a recipe for very smart people being very wrong in very visible/loud ways.

estebank··on Prefer strict tables in SQLite
https://github.com/tursodatabase/turso
estebank··on Postgres rewritten in Rust, now passing 100% of the Postgres regression tests
> And some programmers are so good that some issues are self-explanatory and they write good code to note a thing but don't write a test, because implementing the test is more expensive.

You don't write a test (just) to verify that your change fixed the issue, but to ensure it doesn't regress in the future after an unrelated refactor.

estebank··on Multilingual experience linked to delayed aging
If anything comedy is an excellent way of learning a language: the use of double and triple entendres helps to quickly get exposed to alternate meanings and misunderstandings of words. Comedy aimed at learners or multilinguals can also help, plenty of anglos who learned Spanish can relate to "feeling pregnant" early on :)
estebank··on Multilingual experience linked to delayed aging
Someone who can speak English on top of their mother tongue is already multilingual. Anecdotally, people who live anywhere close to a border in Europe tends to speak at least two languages, often more than two, regardless of class or profession.

In Latin America, most countries speak Spanish (with the obvious exception of Brazil and smaller colonies from the other European countries), so the every day pressure to learn another language isn't there and English becomes the "obvious" choice. I don't quite get why you seem to discount English entirely.

There's always been a Lingua Franca. It hasn't always been the same one. There will likely always be one.

estebank··on How working with a blind client revealed invisible accessibility gaps
You sure come across as calm and collected, looking for a good faith discussion...
estebank··on How working with a blind client revealed invisible accessibility gaps
In cycling threads you always have comments both telling cyclists to stay out of sidewalks and others telling cyclists that they "only* belong in sidewalks (and away from the road they drive in).

It is legal in some places, illegal in as many others, and has caveats almost everywhere (children are almost always allowed, in other places it is based on speed, etc.).

estebank··on How working with a blind client revealed invisible accessibility gaps
We can't individual responsibility our way out of systemic problems. Cyclists on sidewalks generally signals terrible bike infrastructure.

There are people on bikes that ride like an asshole. There are people on cars that drive like an asshole. Both cause (different levels of) risk for pedestrians. There's only so much we can do about assholes, social ostracism works only so far and social change is much harder to accomplish than modifying our built environment to reduce or eliminate conflict points.

As an aside, I've noticed people get startled when I'm on my bike stopped but balancing on my bike while I wait for then to cross. I think some people intuitively model bikes on the same category as cars, so being anywhere close causes them to react as if a car hard crept close.

estebank··on Since Linux 6.9, LUKS suspend stopped wiping disk-encryption keys from memory
Yes, that would be one way of doing it. You can model off of the Typed Builder pattern:

  struct Builder<const A: bool, const B: bool> {
    a: Option<u32>,
    b: Option<u32>,
  }
  struct Val {
    a: u32,
    b: u32,
  }
  impl<const B: bool> Builder<false, B> {
    fn set_a(self, a: u32) -> Builder<true, B> {
      Builder {
        a: Some(a),
        b: self.b,
      }
    }
  }
  impl<const A: bool> Builder<A, false> {
    fn set_b(self, b: u32) -> Builder<A, true> {
      Builder {
        a: self.a,
        b: Some(b),
      }
    }
  }
  impl Builder<true, true> {
    fn build(self) -> Val {
      Val {
        a: self.a.unwrap(),
        b: self.b.unwrap(),
      }
    }
  }
This won't work for everything, but it is a pattern that I find useful to ensure that things can't happen out of order.
estebank··on Since Linux 6.9, LUKS suspend stopped wiping disk-encryption keys from memory
This is in effect a state machine, and when you have a type system more complex than C's you can encode state transitions in the type system (either by having state transitions explicitly return a new return type or by using sum types). You still need to architect the system to encode the invariants in types. No language will fix all logic bugs for free. But you can leverage language features to reduce their number.
estebank··on Physical disc production ending in Jan 2028 for new games on PlayStation
Like DVD before it, Blu-ray includes region locking as a feature (but apparently ~70% of disks don't bother with it, notably Paramount and Universal don't). Non-locked disks are marked as ABC (A is the Americas and Asia, B is Europe/Africa/Aus/NZ, C is Russia/India/China).
estebank··on Physical disc production ending in Jan 2028 for new games on PlayStation
The movie itself is generally encoded at a higher bitrate than what you can find in streaming or torrents.

The media includes bonus features that generally aren't available in streaming or torrents.

The media will not suddenly stop existing if some server breaks down, some company goes under or some contract expires.

The movie will not suddenly get "patched" with an AI-upscale or censored scene one day while watching it.

You can lend the media to someone else to watch without having to ask for permission to anyone else.

estebank··on Xsnow "protestware" in Debian
A lot of people talking about how displaying the flag at all could get somebody in Russia in trouble. You could see the increasing of the likelihood as helpful then, because the flag could always appear. By making it come up more often it communicates to the user that it can appear it gives a chance to the user to notice it in a "safe" situation and not use xsnow if they are in a situation where it could cause trouble for them. The existence of the flag is not quite mentioned at https://www.ratrabbit.nl/ratrabbit/xsnow/, and the site is loading too slow to see if it is shown in https://www.ratrabbit.nl/ratrabbit/xsnow/visuals/index.html.
estebank··on European digital ID wallets rely on safety services of Google and Apple
This is a problem I'm seeing a lot of countries rushing full steam ahead. The age of a single physical ID that's only rarely needed and ubiquitous cash payments seems to be coming to an end. For anyone who travels a lot, migrants first settling in a place, or citizens abroad, this makes things even harder than they already were.
estebank··on GitHub shouldn't be a dependency for publishing Rust on crates.io
This kind of thing is one of the reasons crates.io distributes source only, and that published crate dependencies can't depend on other repositories (that might allow for that attack).
estebank··on GitHub shouldn't be a dependency for publishing Rust on crates.io
Feel free to point at 1 thing that the rust-lang didn't do because the Foundation blocked it.
estebank··on GitHub shouldn't be a dependency for publishing Rust on crates.io
Open source projects tend to be (and Rust certainly is) a showupocracy. Shit gets done when people that care about that shit does it. This means that important stuff that everyone agrees is important but not important enough for me to do, doesn't get done. And that some things end up being 80% solutions that scratch the itch of the person driving a project and progress stalls beyond that.
estebank··on GitHub shouldn't be a dependency for publishing Rust on crates.io
1) Trawl registry for packages owned by domains.

2) Note expired domains and register them yourself.

3) Supply chain compromise.

That, and not wanting people to fork out money for a domain as a requirement to participate in the ecosystem.

estebank··on The deadly rise of giant trucks and SUVs
> Not only does that not have anything to do with texting, but it has nothing to do with the roads.

From both the NYT video and their article:

> And that increase is unique to the US. Most other wealthy countries haven't seen a similar surge, suggesting that possible culprits, like the raise of smartphones, don't tell the whole story.

---

> If you hit someone with a tall-ass truck in Portugal, I'm pretty sure he's going to fall over just like he would in the USA.

How popular do you think the tall-ass trucks are in Portugal?

estebank··on The deadly rise of giant trucks and SUVs
Or, put another way, 1% of every traffic fatality, or 8% of every pedestrian killed by cars. Those are not a small number being affected by a single variable.
estebank··on The deadly rise of giant trucks and SUVs
Because the effects are explicitly called out for US roads and not other locales.

https://m.youtube.com/shorts/FDvTWy0LMt4

← PreviousPage 2 of 34Next →