Overall this looks nice, but I found myself stumbling over the ToSpan syntax:
let span = 5.days().hours(8).minutes(1);
It feels sort of weird how the first number appears in front, and then all the other ones are function arguments. I suppose if you don't like that you can just write: let span = Span::new().days(5).hours(8).minutes(1);
at the expense of a couple characters, which is not too bad.
If only Rust had named function parameters, you could write what is IMHO the most readable option:
could you do that with `struct` / `record` fields? In JavaScript which doesn't have named function parameters either I often write functions with a single `cfg` parameter that are called like `f({ hours: 2, seconds: 53, })` which I find nice b/c it re-uses existing data structures.
In Rust, you can't implicitly omit fields when instantiating a struct, so it would have to be a bit more verbose, explicitly using Rust's analog to the spread syntax.
It would have to look something like:
The defaults could come from some value / function with a name shorter than Default::default(), but it would be less clear.Adding support for struct default field values would allow for
- leaving some mandatory fields
- reduce the need for the builder pattern
- enable the above to be written as f(S { hours: 2, seconds: 53, .. })
If that feature ever lands, coupled with structural/anonymous structs or struct literal inference, you're getting everything you'd want from named arguments without any of the foot guns.
Has anyone ever proposed it? It's such a straightforward feature with such obvious semantics ("default field values are const contexts") and impossible-to-bikeshed syntax (`a: i32 = 42`) that I've been meaning to write up an RFC myself for around, oh, ten years now...
Kind of inefficient. Also it's less ergonomic since every struct is it's own type, so you need to have the signature on both sides.
It should compile to the same because a struct passed by value is loaded into the registers the same as method arguments.
Well the beautiful thing about software engineering is that pretty much everything is possible, it essentially boils down to "but should you really"? :-)
Yes, that could've been lended almost as-is from OCaml, in particular as Rust doesn't have partial application so optional arguments would work out-of-the-box as well.
Are named arguments on the roadmap somewhere? Or is it a won't-fix?
The feature is controversial enough that it's basically a wontfix.
Is it really, though? I didn't read all the comments, but the ones I read, very few were opposing the concept as such: https://internals.rust-lang.org/t/pre-rfc-named-arguments/16...
However, many were opposing overloading the same pre-rfc was suggesting.
I'm guessing that general support doesn't translate to support for a specific syntax with changes to the calling convention. I wouldn't put money on this coming together any time soon.
I haven't seen anything for or against in actual roadmaps. But there is, of course, at least one pre-proposal:
https://internals.rust-lang.org/t/pre-rfc-named-arguments/16...
It doesn't think about optional arguments (but somehow does include overloading). And a bit in a related fashion, it doesn't permit for reordering calling arguments, which I consider a downside:
The rationale for this expressed in the comments says it's incompatible with overloading, but to me I don't see why named arguments and overloading should go hand-in-hand—or, indeed, how desirable overloading is in the first place. Or why should overloading be able to overload that kind of scenario. The other reasons for this don't seem really problems at all.
> func2 and func3 could work in theory: named arguments as proposed in this RFC are position-based and their internal names are different: just like two arguments can have the same type without ambiguity, those functions could be allowed.Maybe there are technical reasons that are simpler when considering type compatibility between function types that have or don't have labeled arguments? Seems the proposal has misunderstood something about OCaml labeled arguments when placing it under https://internals.rust-lang.org/t/pre-rfc-named-arguments/16... , though.
In addition the proposal doesn't seem to have a neat syntax for forwarding named parameters, like in constructing records you can just fill in a field called foo by mentioning its name by itself—or, like in OCaml you can have
If it used the .-prefix as mentioned as an idea elsewhere, then this too could be naturally expressed.Maybe there are other ideas how to go about the labeled arguments, though that one seems pretty well thought-out.
One thing I've enjoyed with Python (and Mypy) is the ability to require the caller to use named arguments with the asterisk marker in the parameter list. This idea is mentioned in the proposal.
I'm all for named parameters. C++ is sorely lacking that feature as well.
Currently using vs code with C++, I like how it handles the missing language feature by adding a grayed out parameter name before the value for function calls and initializers. Maybe there is something like that for rust.
Yes, editors can be configured to do the same thing for rust.
These are called "inlay hints" and exist for most editors/languages.
I stumbled over
I seem to remember Rust does that thing with interfaces instead of classes, is it that? How come I import a library and all of a sudden strings have a `parse()` method that despite its generic name results in a `Timestamp` object? or is it the left-hand side that determines which meaning `str.parse()` should have? What if I have two libraries, one for dates and one for say, Lisp expressions that both augment strings with a `parse()` method? Why use this syntax at all, why not, say, `Timestamp.parse( str )`? I have so many questions.All of these options work and are equivalent.
- let time = Timestamp::parse("2024-07-11T01:14:00Z")?;
- let time: Timestamp = "2024-07-11T01:14:00Z".parse()?;
- let time = "2024-07-11T01:14:00Z".parse::<Timestamp>()?;
You’re free to choose whatever you prefer, although the compiler needs to be able to infer the type of time. If it can’t, it’ll let you know.
So a fourth option is allowed, as long as the subsequent lines make the type of time unambiguous.
- let time = "2024-07-11T01:14:00Z".parse()?;
This is a direct consequence of Timestamp implementing the FromStr trait.
Haha, yes I did. If only the HN textbox integrated with rust-analyzer, it would have caught the mistake.
It's implied. Here is the full syntax.
That ::<TYPE> thing at the end is called a turbofish. It is rarely necessary to give it explicitly (but sometimes you do when the compiler cannot infer the return type on its own—thusfar in my own rust coding I’ve needed it exactly once).
It’s useful to know the full syntax, I’ve definitely encountered needing it more than one time.
It’s because Timestamp implements the FromStr trait which is one of the first traits everyone learns about when learning rust. So when you say that your value is a Timestamp and the expression is string.parse()?, the compiler knows that it has to use the implementation which returns a Timestamp.
There will never be two libraries that clash because of Rust’s orphan rule: you can only implement either a trait which you define on any type, or define a foreign trait on a type which you define, so there’s no way for some random library to also ship an implementation of FromStr for Timestamp
`parse` is actually an inherent method on `str` that always exists: https://doc.rust-lang.org/core/primitive.str.html#method.par...
Its return type is generic, and here it's inferred from the left hand side. It's implemented using the `FromStr` trait, and you can equivalently write `Timestamp::from_str(t)`.
You're thinking of the "extension trait" pattern for using traits to add methods to existing types when the trait in scope, but that's not what's going on here. Jiff's `ToSpan` mentioned above is an example of that pattern, though: https://docs.rs/jiff/latest/jiff/trait.ToSpan.html
Rust will determine what `parse` does based on the inferred return type (which is being explicitly set to `Timestamp` here). This is possible when the return type has `FromStr` trait.
Not being snarky, but I suggest starting by reading at least a little about traits? None of your questions are really about this library - it's just FromStr and an orphan rule.
I agree. Personally, I'd prefer
Looks like it should be supported: https://docs.rs/jiff/latest/jiff/struct.Span.html#impl-Add%3...
That isn't an implementation of addition between Spans and other Spans. It looks like there isn't one in the library right now. `impl<'a> Add<Span> for &'a Zoned` means a borrow of Zoned is on the left hand side, and a Span on the right. So it says that if z is a Zoned (not a Span) and s is a Span, you can do `&z + s` to add a span to a Zoned. There are a bunch of implementations there, DateTime + Span, Date + Span, Time + Span, Offset + Span. All with Span on the right, but none for Span + Span (nor Span + &Span, or &Span + &Span, ...).
This is correct. You can't do a `span1 + span2`. You'd have to use `span1.checked_add(span2)`. The main problem I had with overloading `+` for span addition is that, in order to add two spans with non-uniform units (like years and months), you need a relative datetime. So `+` would effectively have to panic if you did `1.year() + 2.months()`, which seems like a horrific footgun.
It would be plausible to make `+` for spans do _only_ component wise addition, but this would be an extremely subtle distinction between `+` and `Span::checked_add`. To the point where sometimes `+` and `checked_add` would agree on the results and sometimes they wouldn't. I think that would also be bad.
So I started conservative for the time being: no `+` for adding spans together.
Have you checked the API to see if that works? I imagine it does.
I wonder if OP will accept a PR for such a change. Your proposal is much readable and flexible (it's not clear from the docs if you can add random time ranges together). Plus, you'll be able to create your own ranges like `1.decade` or `1.application_timeframe` and add/subtract them.
Yeah, or there could simply be a `days()` free function (and equivalents of the other methods too). No need for struct constructors to be associated functions.
I haven't tried it (so I'm sorry if it's wrong or not what you're talking about) but can't you get a freestanding days function by
You cannot import trait methods as free standing functions. I'm not sure if there was a discussion about making this a possibility but it's definitely not something you can do today.
multiple discussions happened for this and I don't quite remember the outcome.
But it's much less simple then it seems.
Because `use Trait::method` would not be one (potential generic) method but a group of them so it would be it's own kind of thing working differently to free functions etc. Furthermore as generics might be on the trait you might not be able to fill them in with `::<>` and even if you fill them in you also wouldn't be able to get a function pointer without having a way to also specify the type the trait is implemented on.
All of this (and probably more issues) are solvable AFIK but in context of this being a minor UX benefit its IMHO not worth it, 1. due to additional compiler complexity but also due to 2. additional language complexity. Through maybe it will happen if someone really cares about it.
Anyway until then you can always define a free function which just calls the method, e.g. `fn default<T: Default>() -> T { T::default() }`. (Which is probably roughly how `use` on a trait method would work if it where a thing.)
Oh, sorry about that then.
Or 0.days(5).hours(8).minutes(1)?
Cool, that gives us 0-days.
I agree with that. In my thought process is to specify what I’m doing and only then some details. This is the other way around. When reading the code, it would be better to see that I’m dealing with span at first.
Can you do 5.days() + 8.hours() + 1.minutes()?
I like your version's consistency.
The original looks like something Ruby would do.