Closed tryoxiss closed 6 months ago
Generally the language has been moving away from using as
, particularly because it doesn't easily fit into the middle of a call chain like a method conversion does.
Generally the language has been moving away from using
as
, particularly because it doesn't easily fit into the middle of a call chain like a method conversion does.
Ahh, that makes sense then. The only times I have used it are casting numbers with generics, as thw _type
syntax is ugly to me (100 as u8
is cleaner than 100_u8
to me, but since types can be inferred it only comes up with things where it takes a generic number type), and started thinking it was weird it could only be used for primative numbers and nothing else.
if the language is moving away from it I support that fully, either a fully deprecated as
or more functionality. Just feels like a weird middle ground currently. Thanks for the explanation!
Many of the conversions that used to require as
are now available via method. It's not all of them, but the situation will slowly improve as time allows (there's always a million things to work on at once).
I'd say we also want to move away from as
casts because it is somewhat footgunn-y. The footgun aspects of numeric casts are fairly obvious (it does silent truncation), but as
casts also tend to be "too powerful" in certain circumstances. For example, we recently stabilized std::ptr::from_ref
as a way of getting a raw pointer from an &T
, but its use is more constrained than the fully general as
cast that is typically used to convert a &T
to a raw pointer.
The regex
crate is a good example of a library where there was a concerted effort to avoid as
in as many places as possible. The specific reason for doing this is that there are many "ID types" that use u32
to save on space, but also want to index into slices. To do that, those u32
values need to be converted to a usize
, and more importantly, sometimes usize
values need to be converted back to u32
values. I devised a small little abstraction layer to help with that (among other things): https://github.com/rust-lang/regex/blob/a5ae35153a6ec61e64cb297155f7d91c11b629c7/regex-automata/src/util/int.rs
There are still some places where it's impossible/annoying to get rid of as
AFAIK, even with the help of abstraction traits. Namely, in const
contexts, calling trait methods is not allowed.
A side note about as
becoming an alias for .into()
: The naming conventions in the API guidelines state that conversions with "as" should generally be free, whereas "into" may have variable cost. Mapping as
to .into()
would dilute that naming convention, because as
would generally be just as costly as .into()
, causing allocations etc.
But I also agree with the bigger point that as
is something to move away from. I like the clippy lint as_conversions.
I've never understood the "silent truncations" argument. It's not silent, it's written right on the page that a conversion happened.
I've never understood the "silent truncations" argument. It's not silent, it's written right on the page that a conversion happened.
I can't tell if you're quibbling with my wording or if you're objecting to the existence of footguns with as
entirely. If the former, then it seems like it's just a matter of picking different words to describe the underlying problem, of which I'd likely have no objection. But I nevertheless conceptualize the underlying problem as "silent truncation" because truncation is often, but not always, besides the point of what you're trying to accomplish by using as
in the first place. But if you're objecting to the existence of a footgun in the first place, then I'm not sure what you don't understand there. We likely have a deeper philosophical disagreement that probably isn't going to be resolved here.
It's particularly that many people have said that exact pair of words to describe it, which I find puzzling. I guess what I would say is that if you try to shove a u64 into a u32, what else is expected other than that the value will end up in the u32 range, which is less than the u64 range. I'd agree it's a little footgunny, and I'm happy we've got a range of conversion methods, but I've never understood what else people think as
should do on an out of range value than what it currently does.
It's an intent-versus-behavior mismatch. Some times things are confusing because they don't behave as described (not the case here), and some times things are confusing because despite the fact that they behave as described, their behavior is still surprising. As I said above, as
casts tend to couple things together that don't necessarily need to be coupled.
Fully specifying the behavior of a thing can fix some types of confusion and not other types. In this case, "silent truncation" is referring to a mismatch in expectation of behavior, where "expectation" isn't necessarily 100% well informed and correct.
what else is expected other than that the value will end up in the u32 range
It could panic. Or saturate. Or panic-in-debug-mode-and-truncate-in-release. There are lots of different possible behaviors really. I'm not suggesting it ought to do any of those things, but listing out alternative behaviors of what as
could reasonably do.
Thank you everyone for explaining why this is a bad idea in a polite and respectful way. Seeing this now, I am going to close the issue as I now agree that my proposal is a bad idea.
Thank you rust community for explaining why this is bad in a polite and respectful way. Seeing the reasons this is bad, I now fully retract this idea for any further consideration. The original post is kept for archival reasons only.
Currently, you can cast some types to other types using the
as
keyword. For example100_i32 as u32
will convert it to a u32. However, this is a language builtin with no way to interact with it. I porpose a new trait:As<T>
which you can implement for your types.For example if you wanted to cast the string literal
"Some String"
to a string, you would need to do"Some String".to_string()
, but if it hadimpl As<String> for &'static str
you could write"Some String" as String
, essentially making it a shorthand for.into()
, which would be exactly the default implementation. You could also do things like"Some String Slice" as String
,12_u128 as Guid
,[122, 221, 152, 1] as Ipv4Addr
. (Ok this is a disorganised ramble I kept changing the paramaters and its hard to keep track of this stuff. I think its good now?)The reason I propose a new trait, instead of adding to
Into<T>
is that while some things can technechally be cast, it would be a bad idea to, as not every thing that can be converted with.into()
. For example,As
should only be allowed with the conversion cannot fail."String 1234" as i32
can fail because the number can be too big, or not represent a numbe. But[u8; 4] as Ipv4Addr
cannot fail so it would be suitable to implement as.Rust already has traits to interact with defualt operators, such as
std::ops::Mul
, and even for key parts such as functions withFn
,FnMut
andFnOnce
, so I don't think its that far fetched to have this too. Its cannonical home would likely bestd::ops::As
.Possible signature:
This may not be a good idea, so if anyone has comments, suggestions, reasons why its not good, or any other feedback please do reply with it. Just seems weird to me that nearly every feature has a core trait you can implement but
as
does not, and it does not seem to have a good reason for it.