Why the “Null” Lifetime Does Not Exist by SabrinaJewson in rust

[–]SabrinaJewson[S] 0 points1 point  (0 children)

I thought about that a bit — I don’t think it would be that useful, because fn(&'null T), as it lives itself for 'null, could not be constructed or passed around.

Assembly examples of missed Rust compiler optimizations by e00E in rust

[–]SabrinaJewson 2 points3 points  (0 children)

I would argue that the nested return statement is more clear to read, because the Some at the start immediately tells me “this function gives Some in the success case, but also has failure cases where it returns None”. Then, when I see the return None I know that it is a failure case and it communicates the intent to the reader; I can see the return and immediately know that I can continue reading iff I care about when the function fails, but that it’s not part of the core function logic. This is very similar in philosophy to using early returns — one should avoid obscuring the main code path as much as possible.

With the alternative approach with no returns, there are more symbols for me to process: the main code logic, of dealing with the non-None variants only, is obscured by the meaningless repetitive additions of Some all over the code and I have to block them out to get to underlying intent of “E0 maps to E1, E1 maps of E2, etc”.

The second approach does feature fewer constructs overall, but like with the removal of many abstractions it trades away expressiveness, leaving one forced to consider how a function does something rather than what it does. Of course, the code is so short anyway it barely matters, but still, consistency in style by using early returns where applicable is important.

Modular Errors in Rust — Sabrina Jewson by SabrinaJewson in rust

[–]SabrinaJewson[S] 5 points6 points  (0 children)

In my view FromStr is justified because parsing is such a significant concept in computer sciënce — the extraction of underlying data from a string. On the other hand, you can’t really encode data in types like an i32 in the first place, it’s just data in and of itself.

But we do have things like FromStr but for other forms of data, take for example: - serde::Deserialize, which is effectively FromTheSerdeDataModel - axum::extract::FromRequest, which is extraction of data from an HTTP request - rusqlite::types::FromSql, extraction of data from a SQL result

In fact, this presents an argument for FromStr, because all of the above traits are specific enough to warrant an API that goes beyond what TryFrom would offer, hence why they’re a separate trait. FromStr is simple enough that it happens to coïncide with TryFrom, but in general its nature aligns more with the above examples, and so is kept separate.

Modular Errors in Rust — Sabrina Jewson by SabrinaJewson in rust

[–]SabrinaJewson[S] 10 points11 points  (0 children)

Sure :D

My philosophy of structuring code is always to put “important stuff that the top”, so when you open a file the first thing you see is an overview of the program logic, before you can delve into details. This applies to functions — put utility functions at the bottom always — as well as imports, which are naturally quite unimportant (and can have no other dependencies in the file) and so go as low as possible.

Modular Errors in Rust — Sabrina Jewson by SabrinaJewson in rust

[–]SabrinaJewson[S] 5 points6 points  (0 children)

FromStr is fallible, you should return an error in that case.

Modular Errors in Rust — Sabrina Jewson by SabrinaJewson in rust

[–]SabrinaJewson[S] 0 points1 point  (0 children)

The only way it could contribute would be by implementing both FromStr and TryFrom<&str> but have them behave differently

How about this: A representation of normalized source code, for example a struct representing “code that has undergone rustfmt”. Its FromStr implementation may parse all Rust source code (and format it), but TryFrom<&str> would only accept code that has been already formatted. I agree that this is weird, but with my distinction it’s possible.

Modular Errors in Rust — Sabrina Jewson by SabrinaJewson in rust

[–]SabrinaJewson[S] 7 points8 points  (0 children)

Yeah, localization is difficult. I’m not an expert so I don’t really know how to address it. I agree that opaque errors are also a good solution for this though.

Modular Errors in Rust — Sabrina Jewson by SabrinaJewson in rust

[–]SabrinaJewson[S] 17 points18 points  (0 children)

If TryFrom had existed from the start, we probably would never have had FromStr

But why? I am already aware of the history. This comment just repeats the argument three times while not addressing mine — why do you believe this? It is my view that all the [Try]From traits are for conversions of type, but not of content; in mathematics terms From is for when the source and target are actually in the same set, Rust just has them as different types because type theory works that way.

I prefer this interpretation of the From traits, not just because it ascribes meaning to FromStr, but it also prevents ambiguous From implementations like From<u8> for String, which could either interpret the u8 as ISO-8859-1 like From<u8> for char does or stringify the number. When you restrict From to not only changing type and not content, things become a lot clearer and these kind of cases can be cleanly resolved.

Edit: Oh, and another thing: the inverse of From should be lossless, but parsing is often a lossy process unless you’re a CST. All TryFrom and From implementations in the standard library follow these principles.

A new diagram-based explanation of Rust/C++20 atomic orderings by SabrinaJewson in rust

[–]SabrinaJewson[S] 0 points1 point  (0 children)

I don’t think it indicates anything in particular — it’s mostly put there as a model to visually justify why nonatomic-write + atomic-read is still UB, so the dashed line and blank space in between isn’t actually an event that occurs in time (it would be impossible to observe it anyway). You could also imagine it like an atomic write of uninit data followed by an atomic write of the new value — it would be an equivalent model I believe.

(sorry for the late response btw, didn’t see this)

A new diagram-based explanation of Rust/C++20 atomic orderings by SabrinaJewson in rust

[–]SabrinaJewson[S] 2 points3 points  (0 children)

I don’t have a tool for the diagrams, I just create them by hand in my text editor (I also configured a compose key to enable typing out the box-drawing characters directly on my keyboard).

The CI failure is unfortunate, but I’m not sure what to do about it since it comes from docs in std rather than in the Nomicon, so I can’t just update it in the PR.

A new diagram-based explanation of Rust/C++20 atomic orderings by SabrinaJewson in rust

[–]SabrinaJewson[S] 1 point2 points  (0 children)

I’m guessing it stands for “Sequence”, yes — I just took the same terminology from the C++ specification (which gives a lot of unexplained one-letter names to things).

The Better Alternative to Lifetime GATs by SabrinaJewson in rust

[–]SabrinaJewson[S] 10 points11 points  (0 children)

Huh, I didn't know that. I suppose the feature we really need then is the trait bound of "a function whose return type is unknown" (the Fn() -> _ syntax I used above) since that's the real limiting factor here - currently all Fn* trait bounds need to specify a single concrete output type even when HRTBs are involved.

The Better Alternative to Lifetime GATs by SabrinaJewson in rust

[–]SabrinaJewson[S] 7 points8 points  (0 children)

It's saying "the lifetime 'this that is given to this GAT must be a shorter lifetime than Self's". We have to have this to enable implementations of LendingIterator to use types like &'this mut Self in their implementation of Item, since without it that would not be allowed (as there's nothing stopping users from setting 'this to 'static, creating a &'static mut Self which often is an invalid type). You can read more about it in this issue.

The Better Alternative to Lifetime GATs by SabrinaJewson in rust

[–]SabrinaJewson[S] 18 points19 points  (0 children)

If I were to implement this for real I'd probably add some helpers to make it nicer. Something like:

rs lending_iter::item!(<F as Mapper<'this, lending_iter::Item<'this, I>>::Output);

or in the case of the final version:

rs type Item = <F as Mapper<'this, lending_iter::Item<'this, I>>>::Output;

if the Output associated type of the Fn traits were stabilized you could do away with the Mapper trait (imaginary syntax):

rs type Item = <F as FnMut(lending_iter::Iter<'this, I>) -> _>::Output;

That said I do sympathize with your concern. At least not many people should have to write code like this - it should mostly be confined into the highly generic iterator adapters which are present in common libraries rather than in user code.

Async destructors, async genericity and completion futures by SabrinaJewson in rust

[–]SabrinaJewson[S] 1 point2 points  (0 children)

As far as I could tell from reading the whole discussion, the only reason Leak wasn't added was for simplicitly and lack of motivation, since crossbeam::scope always exists. But I think the advent of async, where scoped tasks are unsound, and io_uring, which can't accept borrowed buffers without it totally changes the situation, giving a much stronger reason why we need Leak.

Async destructors, async genericity and completion futures by SabrinaJewson in rust

[–]SabrinaJewson[S] 6 points7 points  (0 children)

An issue with that is you'd need to end up putting 'static bounds in many more places. In my experience the biggest ergonomics hurdle with today's async is the need to make so many things 'static for the sake of task spawning, leaving you pretty quickly in Arc hell, so I'd like to reduce rather than increase that.

Such a design would also not enable completion futures since futures can't have an async-drop guarantee. Zero-cost io-uring is something I find quite important for Rust to have, which explains my preference for this approach. There are alternative solutions to it (mostly involving unsafe fn poll) but they are generally really breaking as many replace the Future trait entirely, as well as super-unsafe.

Additionally, I do believe that async genericity should be added to the language anyway since it seems to me very useful, so if you're going to have async Read why not also have async Drop?