Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Make the as keyword consider Into Trait implementations #2308

Closed

Conversation

Kerollmops
Copy link
Contributor

@Kerollmops Kerollmops commented Jan 21, 2018

Permit to use the common as keyword with any type that implement the Into Trait,
allowing explicit conversions whose primitives already benefit, more visible than simple function calls.

Rendered

struct Foo(i32);

struct Bar(i32);

impl Into<Bar> for Foo {
    fn into(self) -> Bar {
        Bar(self.0)
    }
}

// this actual syntax
let x = Foo(42);
let y: Bar = x.into();

// is equivalent to the new one
let x = Foo(42);
let y = x as Bar;

#2308 (comment) In this RFC I propose to keep the actual warnings and errors that the Into trait already emit on possible lossy conversions and apply these to the actual as keyword, combining the two features and removing possible confusions.

This keyword is used in the disambiguation of function calls and also in the renaming imports. But it seems those two usage are unrelated to the one I want to change, this is like another keyword with the same name.

// these usages are not related

// import renaming
use std::thing::Foo as Bar;

fn main() {
    // function call disambiguation
    <Foo as Pretty>::print(&f);
}

@Kerollmops Kerollmops changed the title RFC: Add the as keyword consider Into Trait implementations RFC: Make the as keyword consider Into Trait implementations Jan 21, 2018
@Kerollmops Kerollmops force-pushed the as-keyword-consider-into-trait branch 3 times, most recently from 94fe115 to 486cbcc Compare January 21, 2018 16:30
@Kerollmops Kerollmops force-pushed the as-keyword-consider-into-trait branch from 486cbcc to 1285b70 Compare January 21, 2018 16:32
// is a syntax sugar for
let x = Foo(42);
let y = Into::<Bar>::into(x);
```
Copy link
Contributor

@jonas-schievink jonas-schievink Jan 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't work since as is not equivalent to a call to .into() - for example, as can convert from f32 to u32, while .into() can't (this is a lossy conversion, so .into() shouldn't work here).

Copy link
Contributor Author

@Kerollmops Kerollmops Jan 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what is the rule of as conversion and into conversion ?

I think this is related to this thread about lossy conversions between as and into (I linked it in the Rendered).

@Kerollmops
Copy link
Contributor Author

Kerollmops commented Jan 21, 2018

Here is more informations on the actual as behavior.

@Centril Centril added the T-lang Relevant to the language team, which will review and decide on the RFC. label Jan 21, 2018
@durka
Copy link
Contributor

durka commented Jan 21, 2018

I would rather deprecate as in favor of trait-based conversions like Into and TryInto which can be used in generics and you know whether you're getting a lossless conversion, etc.

@hadronized
Copy link
Contributor

hadronized commented Jan 21, 2018

Not sure I want this, since there’s a difference between a cast/coercion and a conversion.

@durka
Copy link
Contributor

durka commented Jan 21, 2018 via email

@hadronized
Copy link
Contributor

@durka isn’t that unsafe anyway?

@hadronized
Copy link
Contributor

Oh yeah, no warning nor error. It should be! 😮

@Kerollmops
Copy link
Contributor Author

Here is more information on the error emmited by the compiler on lossy conversions, TLDR; no error nor warnings with the as keyword, only for the Into::into method.

@Kerollmops
Copy link
Contributor Author

The as keyword isn't a coercion, this is an explicit conversion/cast and therefore should have warnings/errors.

In this RFC I propose to keep the actual warnings and errors that the Into trait already emit on possible lossy conversions and apply these to the actual as keyword, combining the two features and removing possible confusions.

@Kerollmops
Copy link
Contributor Author

@durka Why not keeping both good parts of these features ? Keeping the simple as keyword and keeping the warnings and errors of the trait-based conversions like Into ?

@durka
Copy link
Contributor

durka commented Jan 22, 2018 via email

@Kerollmops
Copy link
Contributor Author

Kerollmops commented Jan 22, 2018

That was actually just a little idea in my head, just wanted to have some more discussion about it.

I think you are right that’s not as simple as « just combine them » but you talked about deprecating it, what about reusing it ? 😀

@durka
Copy link
Contributor

durka commented Jan 22, 2018 via email

@synek317
Copy link

I think that if we unify cheap as and expensive Into, then we could also make all function parameters be Into<T> instead of T. But this is not going to happen because rust wants to be explicit when it comes to the costs (and I think it is good).

TryInto makes things even more complicated. It feels like it shouldn't be omited if as would mean into but it is not clear how to deal with Result.

@Manishearth
Copy link
Member

In clippy we actually recommend folks use Into instead of as for numbers because Into is always lossless (so it will produce a compiler error if the types silently change)

@hadronized
Copy link
Contributor

Yeah, that’s actually what we do in Haskell: we use typeclasses’ methods, casts / coercions are not directly accessible via an operator baked in the language.

@alekratz
Copy link

My understanding of as is that it tells the compiler that you want to change your perspective on something that you've got, usually with minimal or no conversion (like bit widening/bit truncation, or pointer conversion). On the other hand, Into may be a simple free conversion or move -- or it may make allocations and be more expensive.

I haven't hacked on the compiler and looked at how as works, but personally, it feels like it would be overstepping what as is intended to do.

@Kixunil
Copy link

Kixunil commented Jan 23, 2018

Slightly different idea: make a LossyInto trait for all types that can use as for conversions. Not sure how useful it is, but I think I've run once into needing such trait.

@jonas-schievink
Copy link
Contributor

Into is always lossless

I've found this to be a useful guarantee, and the existing impls in libstd certainly comply with this, but the Into docs don't require or even mention this (same for From). Is that intentional (ie. it only happens to work out this way for numeric conversion impls in std, other crates are free to implement lossy conversions), or is this just a documentation shortcoming?

@Kerollmops
Copy link
Contributor Author

Kerollmops commented Jan 23, 2018

I forgot about another usage of as for disambiguating function calls.
The Into Trait doesn't seems to handle that.

@Kerollmops
Copy link
Contributor Author

Kerollmops commented Jan 23, 2018

There is an interresting comment about the compiler not emiting enough warnings/errors about unsafe casts/coercions.

I propose to use the mem::transmute function to do unsafe conversions like enum to u8.

EDIT: transmuting here is probably not possible because of the two possible different memory sizes.

@jonas-schievink
Copy link
Contributor

jonas-schievink commented Jan 23, 2018

enum to u8 is a safe operation that doesn't lose any information. mem::transmute is wildly unsafe and should only be used when absolutely necessary.

@Kerollmops
Copy link
Contributor Author

What about an enum with more than 256 variants ? This conversion can be lossy.

@jonas-schievink
Copy link
Contributor

True, but that's very untypical (and still safe)

@ssokolow
Copy link

ssokolow commented Jan 23, 2018

What about an enum with more than 256 variants ? This conversion can be lossy.

True, but that's very untypical (and still safe)

It never occurred to me that Rust would allow such a thing, given past experience with things like DOS game installers that refuse to complete within a typical DOSBox setup because their signed integer free space variable wrapped around to the negative when encountering DOSBox's representation of the free space on a modern-sized hard drive.

Is there a warning (core or clippy) that I can deny in my project boilerplate?

@jonas-schievink
Copy link
Contributor

jonas-schievink commented Jan 23, 2018

Clippy has a pretty nice overview for all the Lints. It has lints for possible truncation, wrap, sign and even precision loss.

@ssokolow
Copy link

ssokolow commented Jan 23, 2018

Ahh, so it's handled like any other int-to-int cast would be with regards to lints? (I think of enums as being distinct from regular ints when it comes to the kinds of guarantees you want to enforce on them, so that possibility didn't occur to me.)

In that case, I'll have to think about whether I want to bump those lints up to deny in the "turn on all clippy lints, then whitelist away the ones I don't need" default I currently set.

@hadronized
Copy link
Contributor

Please do not use sarcasm in a RFC’s discussion section; it conveys no useful meaning nor material to step forwards.

Casting such a big enum to a u8 would be unsafe in any possible ways and a programmer must know about the size of the variants they’re using – here, u16 would be more adapted. Also, mem::transmute is one of the most unsafe construct / function in Rust. You should never use it, except if you have no other choices (there are plenty here). Plus, if your type is repr(C), with a specific alignment, or whatever memory hints, you might just shoot in your feet.

enum with explicit discriminators and cast to i32 for instance is something that has been supported for a while.

@Kerollmops
Copy link
Contributor Author

Kerollmops commented Jan 27, 2018

My message is not appropriate I have to admit. I just removed it and add a playground to show the actual behavior of casting a big enum to a smaller/un-adapted primitive.

This is not unsafe, it's more an unspecified behavior or something like that I would say. I will repeat myself but we must add more warnings in the compiler.

Using mem::transmute, as I said, is completely unsafe and is too verbose to perform this simple action, they added FromPrimitive in 2013 and they removed it in 2014, there were many discussions about that.

The FromPrimitive and ToPrimitive have finally moved to the num crate.

@scottmcm scottmcm added the T-libs-api Relevant to the library API team, which will review and decide on the RFC. label Feb 7, 2018
@scottmcm
Copy link
Member

scottmcm commented Feb 7, 2018

Tagging T-libs as well, since the most-👍'd alternative is library additions.

@nikomatsakis
Copy link
Contributor

@rfcbot fcp close

I'm going to propose closing this RFC -- though not because I'm opposed to the general idea. I just don't think the specifics of this proposal feel right. I've not ready the comment thread in detail, and I'm sure the following points have been made, but I'll repeat:

  • The as operator is probably not something that we want to encourage people to use.
  • We have so far only allowed "operator overloading" in cases where the signature is somewhat limiting, to discourage abuse.

This seems to violate both of those rules, since the Into signature is quite permissive.

(I would perhaps be in favor of some other proposals -- presuming they could be made to work technically -- such as AsRef coercions, where &T is coerced to &U if T: AsRef<U>. This would conceptually generalize our existing Deref coercions (though not literally -- things can be Deref that are not AsRef, right?).)

@rfcbot
Copy link
Collaborator

rfcbot commented Mar 1, 2018

Team member @nikomatsakis has proposed to close this. The next step is review by the rest of the tagged teams:

No concerns currently listed.

Once a majority of reviewers approve (and none object), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added the proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. label Mar 1, 2018
@rfcbot
Copy link
Collaborator

rfcbot commented Mar 15, 2018

🔔 This is now entering its final comment period, as per the review above. 🔔

@rfcbot rfcbot added final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. and removed proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. labels Mar 15, 2018
@rfcbot
Copy link
Collaborator

rfcbot commented Mar 25, 2018

The final comment period is now complete.

@scottmcm
Copy link
Member

Closing as nothing new has happened during FCP. Thanks for the RFC, @Kerollmops!

@scottmcm scottmcm closed this Mar 25, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. T-lang Relevant to the language team, which will review and decide on the RFC. T-libs-api Relevant to the library API team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.