From 35e8e5f3d5c2cdedd3d22d064eb1c7715ce235d6 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Sun, 19 Jul 2015 21:40:24 +0300 Subject: [PATCH 1/7] RFC: Checked integer conversions --- text/0000-integer-conversions.md | 171 +++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 text/0000-integer-conversions.md diff --git a/text/0000-integer-conversions.md b/text/0000-integer-conversions.md new file mode 100644 index 00000000000..44ff3df4c5a --- /dev/null +++ b/text/0000-integer-conversions.md @@ -0,0 +1,171 @@ +- Feature Name: integer_casts +- Start Date: 2015-07-15 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary + +Use traits `Into`/`From` for lossless conversions between built-in integer types. Implement new +traits for checked and wrapping casts for potentially lossy integer conversions analogous to checked +and wrapping arithmetic operations. + +# Motivation + +It is useful to separate lossless integer conversions requiring minimal attention from a programmer +from potentially lossy conversions requiring more thought and some analysis. Currenly all integer +conversions are performed with operator `as` which acts as a Swiss knife and can convert anything to +anything without any precautions thus requiring extra attention from a programmer. + +Besides, overflow checks proved to be useful for arithmetic operations, they can also be useful for +casts, but operator `as` doesn't perform such checks and just truncates (wraps) the results. +The proposed new methods allow to separate potentially lossy conversions into intentionally wrapping +casts and casts where truncation should be considered an error. + +# Detailed design + +## Implement `Into`/`From` for integer types + +Assume T and U are built-in integer types, then `Into` is implemented for `T` and `From` is +implemented for `U` iff the conversion from `T` to `U` is always lossless. +A good visualization (without `usize` and `isize`) can be found [1](here). +Implementations for `usize` and `isize` are platform dependent. This is by design. If code is ported +to a new platform and some of `into()` conversions are not lossless anymore, they have to be +reviewed and replaced with checked casts. The porting effort shouldn't be large and potentially +caught mistakes can easily outweight it, for example, porting Rust codebase from 64-bit Windows to +32-bit Linux took minimal amount of time (see the Implementation section). + +## Introduce new trait `IntegerCast` into `std::num` (and `core::num`) + +``` +trait IntegerCast { + fn cast(self) -> Target; + fn wrapping_cast(self) -> Target; + fn overflowing_cast(self) -> (Target, bool); + fn saturating_cast(self) -> Target; +} +``` + +The methods are analogous to methods for arithmetic operations like `add`/`wrapping_add`/ +`overflowing_add`/`saturating_add`. +`cast()` is equivalent to `as` but panics when the conversion is lossy and debug assertions are on. +`wrapping_cast()` is completely equivalent to `as`, it wraps (=truncates) the value. +`overflowing_cast()` is equivalent to `as` but also supplies overflow flag as a second result. +`saturating_cast()` clamps the value into the range of the target type. +Statistically, `cast()` is the most common of these four methods, `wrapping_cast()` is less common +and usually related to hashes, random numbers or serialization, and the other methods are rare and +highly specialized. + +`IntegerCast` is implemented for all pairs of built-in integer types including pairs with lossless +conversion (this is needed for portability, some conversions can be lossless on one platform and +potentially lossy on other). + +## Make `usize` default type parameter for `Index`, `IndexMut`, `Shl` and `Shr` + +It will make type inference in index position possible in cases like: +``` +let a: u16 = 10; +let b = c[a.into()]; +``` + +## Bonus: Conversions between integers and raw pointers +With flat memory model and no validity guarantees raw pointers are essentially weird integers and +they are relatively often converted to normal integers (usually `usize`) and back with operator +`as`. However, operator `as` can truncate a pointer even to `i8` without blinking an eye, so it +would be nice to have a checked conversion method instead of it - the checking in this case is +purely compile time. As a bonus, the conversion method doesn't usually require specifying the target +`usize`. + +``` +// in core::ptr +trait AsInteger { + fn as_integer(self) -> Target; +} + +impl AsInteger<$Target> for *const T { + fn as_integer(self) -> $Target{ self as $Target } +} +impl AsInteger<$Target> for *mut T { + fn as_integer(self) -> $Target{ self as $Target } +} + +// in core::num +trait AsPtr { + fn as_ptr() -> *const T; + fn as_mut_ptr() -> *mut T; +} + +impl AsPtr for $Source { + fn as_ptr() -> *const T { $Source as *const T } + fn as_mut_ptr() -> *mut T { $Source as *mut T } +} +``` + +where `$Target` and `$Source` belong to the set `{usize, isize, underlying_type(usize), +underlying_type(isize)}`. `underlying_type(T)` here is a fixed-size type equivalent to T, for +example `underlying_type(usize) == u64` on 64-bit platforms. +`as_ptr` and `as_mut_ptr` can arguably be inherent methods. + +## Implementation +An experiment implementing similar but somewhat different design and evaluating its practical +impact is described [2](here). + +# Drawbacks + +This design is not fully ergonomic without type inference fallback based on default type parameters +(sometimes `into()` and `cast()` need redundant type hints) and without type ascription (there's no +way to give a type hint for the target type of `Into` inline). The first problem is currently being +addressed in pending pull request, its solution will reduce the need in type hints to the minimum. +The second problem will hopefully be resolved too in the near future. + +# Alternatives + +1. Do nothing. No one will use the new methods anyway because the built-in alternative - `as` - is +so short and convenient and doesn't require any imports and even works in constant expressions, +and overflows never happen in code written by a reasonable programmer. + +2. Use a separate trait for lossless conversions instead of `Into`, e.g. + +pub trait Widen: Sized { + fn widen(self) -> Target; +} + +It would still make sense to implement `Into` for lossless integer conversions, because they are +totally reasonable conversions and `Into` is by definition a trait for, well, reasonable +conversions. In this case a separate trait `Widen` would feel like a duplication. +The trait `Widen` will have to live in the prelude, just like `Into`, otherwise it will be rarely +used, because the alternative (`as`) doesn't require importing anything (something similar already +happens with `ptr::null` vs `0 as *const T`). Adding new names to the prelude may be considered a +drawback. + +3. Core language solution for lossless conversions, e.g. new operator `as^` or `lossless_as`. +This is much more intrusive and doesn't probably pull its weight. +It would still make sense to implement `Into` for lossless integer conversions, because they are +reasonable conversions. There's a non-zero chance that `Into` will get its own language sugar +somewhere in the remote future. + +4. Make lossless integer conversions implicit at language level. This alternative is not pursued. +In the relevant thread on internals many people spoke against this alternative and it had no +consensus. Moreover, originally the absence of these conversions is [4](by design) and not just an +omission. + +5. Methods `as()`/`wrapping_as()/...` may look better but `as` can't be used as a method name. +Theoretically `as` can be made a context dependent keyword, then the names will become available. + +6. `IntegerCast` can be splitted into several traits - `IntegerCast/WrappingIntegerCast/...`, but +there's not much sense in multiplying entities - `IntegerCast` is ought to be implemented for a +limited set of types and all its methods always go in group. + +7. Sign conversions with fixed target type described in [2] are subsumed by `IntegerCast` in this +design, but they can probably be useful by themselves. They would also have to be provided in +several variants - `as_signed()/as_signed_wrapping()/...`. + +8. Some alternative names: `as` -> `to`, `cast` -> `conv`. Bikeshedding is welcome. + +# Unresolved questions + +None so far + +[1] https://internals.rust-lang.org/t/implicit-widening-polymorphic-indexing-and-similar-ideas/1141/45 +[2] https://internals.rust-lang.org/t/implicit-widening-polymorphic-indexing-and-similar-ideas/1141/70 +[3] https://internals.rust-lang.org/t/implicit-widening-polymorphic-indexing-and-similar-ideas/1141 +[4] http://graydon2.dreamwidth.org/2015/07/03/ From a0b16d61db9abe92b8440289a47a3e3ee1e608a7 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Sun, 19 Jul 2015 22:20:14 +0300 Subject: [PATCH 2/7] Markdown tweaks --- text/0000-integer-conversions.md | 54 +++++++++++++++++--------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/text/0000-integer-conversions.md b/text/0000-integer-conversions.md index 44ff3df4c5a..5d4c68403b0 100644 --- a/text/0000-integer-conversions.md +++ b/text/0000-integer-conversions.md @@ -1,5 +1,5 @@ - Feature Name: integer_casts -- Start Date: 2015-07-15 +- Start Date: 2015-07-19 - RFC PR: (leave this empty) - Rust Issue: (leave this empty) @@ -27,8 +27,9 @@ casts and casts where truncation should be considered an error. Assume T and U are built-in integer types, then `Into` is implemented for `T` and `From` is implemented for `U` iff the conversion from `T` to `U` is always lossless. -A good visualization (without `usize` and `isize`) can be found [1](here). -Implementations for `usize` and `isize` are platform dependent. This is by design. If code is ported +A good visualization (without `usize` and `isize`) can be found [here][1]. + +Implementations for `usize` and `isize` are platform dependent by design. If code is ported to a new platform and some of `into()` conversions are not lossless anymore, they have to be reviewed and replaced with checked casts. The porting effort shouldn't be large and potentially caught mistakes can easily outweight it, for example, porting Rust codebase from 64-bit Windows to @@ -47,10 +48,11 @@ trait IntegerCast { The methods are analogous to methods for arithmetic operations like `add`/`wrapping_add`/ `overflowing_add`/`saturating_add`. -`cast()` is equivalent to `as` but panics when the conversion is lossy and debug assertions are on. -`wrapping_cast()` is completely equivalent to `as`, it wraps (=truncates) the value. -`overflowing_cast()` is equivalent to `as` but also supplies overflow flag as a second result. -`saturating_cast()` clamps the value into the range of the target type. +- `cast()` is equivalent to `as` but panics when the conversion is lossy and debug assertions are on. +- `wrapping_cast()` is completely equivalent to `as`, it wraps (=truncates) the value. +- `overflowing_cast()` is equivalent to `as` but also supplies overflow flag as a second result. +- `saturating_cast()` clamps the value into the range of the target type. + Statistically, `cast()` is the most common of these four methods, `wrapping_cast()` is less common and usually related to hashes, random numbers or serialization, and the other methods are rare and highly specialized. @@ -61,7 +63,7 @@ potentially lossy on other). ## Make `usize` default type parameter for `Index`, `IndexMut`, `Shl` and `Shr` -It will make type inference in index position possible in cases like: +These traits don't currently have default type parameters and setting them will make type inference in index position possible in cases like: ``` let a: u16 = 10; let b = c[a.into()]; @@ -71,7 +73,7 @@ let b = c[a.into()]; With flat memory model and no validity guarantees raw pointers are essentially weird integers and they are relatively often converted to normal integers (usually `usize`) and back with operator `as`. However, operator `as` can truncate a pointer even to `i8` without blinking an eye, so it -would be nice to have a checked conversion method instead of it - the checking in this case is +would be nice to have a dedicated checked conversion method instead of it - the checking in this case is purely compile time. As a bonus, the conversion method doesn't usually require specifying the target `usize`. @@ -101,13 +103,13 @@ impl AsPtr for $Source { ``` where `$Target` and `$Source` belong to the set `{usize, isize, underlying_type(usize), -underlying_type(isize)}`. `underlying_type(T)` here is a fixed-size type equivalent to T, for -example `underlying_type(usize) == u64` on 64-bit platforms. +underlying_type(isize)}`. `underlying_type(T)` here is a fixed-size type equivalent of `T`, for +example `underlying_type(usize) == u64` on 64-bit platforms. `as_ptr` and `as_mut_ptr` can arguably be inherent methods. ## Implementation An experiment implementing similar but somewhat different design and evaluating its practical -impact is described [2](here). +impact is described [here][2]. # Drawbacks @@ -125,27 +127,29 @@ and overflows never happen in code written by a reasonable programmer. 2. Use a separate trait for lossless conversions instead of `Into`, e.g. -pub trait Widen: Sized { - fn widen(self) -> Target; -} + ``` + pub trait Widen: Sized { + fn widen(self) -> Target; + } + ``` -It would still make sense to implement `Into` for lossless integer conversions, because they are + It would still make sense to implement `Into` for lossless integer conversions, because they are totally reasonable conversions and `Into` is by definition a trait for, well, reasonable conversions. In this case a separate trait `Widen` would feel like a duplication. -The trait `Widen` will have to live in the prelude, just like `Into`, otherwise it will be rarely +The trait `Widen` will have to live in the prelude, like `Into`, otherwise it will be rarely used, because the alternative (`as`) doesn't require importing anything (something similar already happens with `ptr::null` vs `0 as *const T`). Adding new names to the prelude may be considered a drawback. -3. Core language solution for lossless conversions, e.g. new operator `as^` or `lossless_as`. -This is much more intrusive and doesn't probably pull its weight. +3. Core language solution for lossless conversions, e.g. new operator `as^` or `lossless_as` or +unary plus. This is much more intrusive and doesn't probably pull its weight. It would still make sense to implement `Into` for lossless integer conversions, because they are reasonable conversions. There's a non-zero chance that `Into` will get its own language sugar somewhere in the remote future. 4. Make lossless integer conversions implicit at language level. This alternative is not pursued. In the relevant thread on internals many people spoke against this alternative and it had no -consensus. Moreover, originally the absence of these conversions is [4](by design) and not just an +consensus. Moreover, originally the absence of these conversions is [by design][4] and not just an omission. 5. Methods `as()`/`wrapping_as()/...` may look better but `as` can't be used as a method name. @@ -155,7 +159,7 @@ Theoretically `as` can be made a context dependent keyword, then the names will there's not much sense in multiplying entities - `IntegerCast` is ought to be implemented for a limited set of types and all its methods always go in group. -7. Sign conversions with fixed target type described in [2] are subsumed by `IntegerCast` in this +7. Sign conversions with fixed target type described in [the experiment][2] are subsumed by `IntegerCast` in this design, but they can probably be useful by themselves. They would also have to be provided in several variants - `as_signed()/as_signed_wrapping()/...`. @@ -165,7 +169,7 @@ several variants - `as_signed()/as_signed_wrapping()/...`. None so far -[1] https://internals.rust-lang.org/t/implicit-widening-polymorphic-indexing-and-similar-ideas/1141/45 -[2] https://internals.rust-lang.org/t/implicit-widening-polymorphic-indexing-and-similar-ideas/1141/70 -[3] https://internals.rust-lang.org/t/implicit-widening-polymorphic-indexing-and-similar-ideas/1141 -[4] http://graydon2.dreamwidth.org/2015/07/03/ +[1]: https://internals.rust-lang.org/t/implicit-widening-polymorphic-indexing-and-similar-ideas/1141/45 +[2]: https://internals.rust-lang.org/t/implicit-widening-polymorphic-indexing-and-similar-ideas/1141/70 +[3]: https://internals.rust-lang.org/t/implicit-widening-polymorphic-indexing-and-similar-ideas/1141 +[4]: http://graydon2.dreamwidth.org/2015/07/03/ From c0372dafb58a642ee0a80e402ea26efc6aac0a2f Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Tue, 21 Jul 2015 20:34:56 +0300 Subject: [PATCH 3/7] Add missing `checked_cast` --- text/0000-integer-conversions.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/text/0000-integer-conversions.md b/text/0000-integer-conversions.md index 5d4c68403b0..10af17d84e2 100644 --- a/text/0000-integer-conversions.md +++ b/text/0000-integer-conversions.md @@ -41,15 +41,17 @@ caught mistakes can easily outweight it, for example, porting Rust codebase from trait IntegerCast { fn cast(self) -> Target; fn wrapping_cast(self) -> Target; + fn checked_cast(self) -> Option; fn overflowing_cast(self) -> (Target, bool); fn saturating_cast(self) -> Target; } ``` -The methods are analogous to methods for arithmetic operations like `add`/`wrapping_add`/ -`overflowing_add`/`saturating_add`. +The methods correspond to methods for arithmetic operations like `add`/`wrapping_add`/ +`checked_add`/`overflowing_add`/`saturating_add`. - `cast()` is equivalent to `as` but panics when the conversion is lossy and debug assertions are on. - `wrapping_cast()` is completely equivalent to `as`, it wraps (=truncates) the value. +- `checked_cast()` returns `None` if the conversion is lossy and `Some(self as Target)` otherwise. - `overflowing_cast()` is equivalent to `as` but also supplies overflow flag as a second result. - `saturating_cast()` clamps the value into the range of the target type. @@ -66,7 +68,8 @@ potentially lossy on other). These traits don't currently have default type parameters and setting them will make type inference in index position possible in cases like: ``` let a: u16 = 10; -let b = c[a.into()]; +let b = c[a.into()]; // With default type parameter +let b = c[a.into(): usize]; // Without default type parameter, but with type ascription, still more verbose than necessary ``` ## Bonus: Conversions between integers and raw pointers From 197c80d040ec0739f01512a3fb9641fe26692282 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Sat, 1 Aug 2015 17:12:19 +0300 Subject: [PATCH 4/7] Remove non-essential stuff Integer-pointer conversions haven't gained much support and the defaults for `Shl` and `Shr` are not necessary for conversions --- text/0000-integer-conversions.md | 40 +------------------------------- 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/text/0000-integer-conversions.md b/text/0000-integer-conversions.md index 10af17d84e2..6533c2d0e6d 100644 --- a/text/0000-integer-conversions.md +++ b/text/0000-integer-conversions.md @@ -63,7 +63,7 @@ highly specialized. conversion (this is needed for portability, some conversions can be lossless on one platform and potentially lossy on other). -## Make `usize` default type parameter for `Index`, `IndexMut`, `Shl` and `Shr` +## Make `usize` default type parameter for `Index` and `IndexMut` These traits don't currently have default type parameters and setting them will make type inference in index position possible in cases like: ``` @@ -72,44 +72,6 @@ let b = c[a.into()]; // With default type parameter let b = c[a.into(): usize]; // Without default type parameter, but with type ascription, still more verbose than necessary ``` -## Bonus: Conversions between integers and raw pointers -With flat memory model and no validity guarantees raw pointers are essentially weird integers and -they are relatively often converted to normal integers (usually `usize`) and back with operator -`as`. However, operator `as` can truncate a pointer even to `i8` without blinking an eye, so it -would be nice to have a dedicated checked conversion method instead of it - the checking in this case is -purely compile time. As a bonus, the conversion method doesn't usually require specifying the target -`usize`. - -``` -// in core::ptr -trait AsInteger { - fn as_integer(self) -> Target; -} - -impl AsInteger<$Target> for *const T { - fn as_integer(self) -> $Target{ self as $Target } -} -impl AsInteger<$Target> for *mut T { - fn as_integer(self) -> $Target{ self as $Target } -} - -// in core::num -trait AsPtr { - fn as_ptr() -> *const T; - fn as_mut_ptr() -> *mut T; -} - -impl AsPtr for $Source { - fn as_ptr() -> *const T { $Source as *const T } - fn as_mut_ptr() -> *mut T { $Source as *mut T } -} -``` - -where `$Target` and `$Source` belong to the set `{usize, isize, underlying_type(usize), -underlying_type(isize)}`. `underlying_type(T)` here is a fixed-size type equivalent of `T`, for -example `underlying_type(usize) == u64` on 64-bit platforms. -`as_ptr` and `as_mut_ptr` can arguably be inherent methods. - ## Implementation An experiment implementing similar but somewhat different design and evaluating its practical impact is described [here][2]. From 380085a8ba247d7b9606d36d11eef3923c264b0d Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Tue, 4 Aug 2015 01:22:36 +0300 Subject: [PATCH 5/7] Rename `IntegerCast` to `IntCast` + Some editorial changes --- text/0000-integer-conversions.md | 37 +++++++++++++++++--------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/text/0000-integer-conversions.md b/text/0000-integer-conversions.md index 6533c2d0e6d..04c21618464 100644 --- a/text/0000-integer-conversions.md +++ b/text/0000-integer-conversions.md @@ -6,7 +6,7 @@ # Summary Use traits `Into`/`From` for lossless conversions between built-in integer types. Implement new -traits for checked and wrapping casts for potentially lossy integer conversions analogous to checked +trait for checked and wrapping casts for potentially lossy integer conversions analogous to checked and wrapping arithmetic operations. # Motivation @@ -35,10 +35,10 @@ reviewed and replaced with checked casts. The porting effort shouldn't be large caught mistakes can easily outweight it, for example, porting Rust codebase from 64-bit Windows to 32-bit Linux took minimal amount of time (see the Implementation section). -## Introduce new trait `IntegerCast` into `std::num` (and `core::num`) +## Introduce new trait `IntCast` into `std::num` (and `core::num`) ``` -trait IntegerCast { +trait IntCast { fn cast(self) -> Target; fn wrapping_cast(self) -> Target; fn checked_cast(self) -> Option; @@ -47,19 +47,20 @@ trait IntegerCast { } ``` -The methods correspond to methods for arithmetic operations like `add`/`wrapping_add`/ +The methods correspond to existing methods for arithmetic operations like `add`/`wrapping_add`/ `checked_add`/`overflowing_add`/`saturating_add`. - `cast()` is equivalent to `as` but panics when the conversion is lossy and debug assertions are on. - `wrapping_cast()` is completely equivalent to `as`, it wraps (=truncates) the value. - `checked_cast()` returns `None` if the conversion is lossy and `Some(self as Target)` otherwise. -- `overflowing_cast()` is equivalent to `as` but also supplies overflow flag as a second result. +- `overflowing_cast()` is equivalent to `as` but also supplies overflow flag as a second result (true +on overflow, false otherwise). - `saturating_cast()` clamps the value into the range of the target type. -Statistically, `cast()` is the most common of these four methods, `wrapping_cast()` is less common +Statistically, `cast()` is the most common of these methods, `wrapping_cast()` is less common and usually related to hashes, random numbers or serialization, and the other methods are rare and highly specialized. -`IntegerCast` is implemented for all pairs of built-in integer types including pairs with lossless +`IntCast` is implemented for all pairs of built-in integer types including pairs with lossless conversion (this is needed for portability, some conversions can be lossless on one platform and potentially lossy on other). @@ -80,9 +81,9 @@ impact is described [here][2]. This design is not fully ergonomic without type inference fallback based on default type parameters (sometimes `into()` and `cast()` need redundant type hints) and without type ascription (there's no -way to give a type hint for the target type of `Into` inline). The first problem is currently being -addressed in pending pull request, its solution will reduce the need in type hints to the minimum. -The second problem will hopefully be resolved too in the near future. +way to give a type hint for the target type of `Into` inline). The first problem is currently resolved, +but the solution is feature-gated, removal of the feature-gate will reduce the need in type hints to +the minimum. The second problem will hopefully be resolved too in the near future. # Alternatives @@ -117,19 +118,21 @@ In the relevant thread on internals many people spoke against this alternative a consensus. Moreover, originally the absence of these conversions is [by design][4] and not just an omission. -5. Methods `as()`/`wrapping_as()/...` may look better but `as` can't be used as a method name. -Theoretically `as` can be made a context dependent keyword, then the names will become available. +5. Methods `as()`/`wrapping_as()/...` may look better than `cast()`/`wrapping_cast()/...`, but `as` +can't be used as a method name. Theoretically `as` can be made a context dependent keyword, then the +names will become available. -6. `IntegerCast` can be splitted into several traits - `IntegerCast/WrappingIntegerCast/...`, but -there's not much sense in multiplying entities - `IntegerCast` is ought to be implemented for a +6. `IntCast` can be splitted into several traits - `IntCast/WrappingIntCast/...`, but +there's not much sense in multiplying entities - `IntCast` is ought to be implemented for a limited set of types and all its methods always go in group. -7. Sign conversions with fixed target type described in [the experiment][2] are subsumed by `IntegerCast` in this +7. Diverge from arithmetic operations and always panic in `cast()`, not only with enabled assertions. +This would make `cast()` equivalent to `checked_cast().unwrap()`. + +8. Sign conversions with fixed target type described in [the experiment][2] are subsumed by `IntCast` in this design, but they can probably be useful by themselves. They would also have to be provided in several variants - `as_signed()/as_signed_wrapping()/...`. -8. Some alternative names: `as` -> `to`, `cast` -> `conv`. Bikeshedding is welcome. - # Unresolved questions None so far From 74af5780dd775e7c6d31b12483a7c404ca75d59b Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Fri, 23 Oct 2015 01:23:55 +0300 Subject: [PATCH 6/7] Remove `Into`/`From` --- text/0000-integer-conversions.md | 93 +++++++++----------------------- 1 file changed, 25 insertions(+), 68 deletions(-) diff --git a/text/0000-integer-conversions.md b/text/0000-integer-conversions.md index 04c21618464..568500d1fe4 100644 --- a/text/0000-integer-conversions.md +++ b/text/0000-integer-conversions.md @@ -5,35 +5,20 @@ # Summary -Use traits `Into`/`From` for lossless conversions between built-in integer types. Implement new -trait for checked and wrapping casts for potentially lossy integer conversions analogous to checked -and wrapping arithmetic operations. +Implement new trait `IntCast` for checked and wrapping casts for potentially lossy integer conversions +analogous to checked and wrapping arithmetic operations. # Motivation -It is useful to separate lossless integer conversions requiring minimal attention from a programmer -from potentially lossy conversions requiring more thought and some analysis. Currenly all integer -conversions are performed with operator `as` which acts as a Swiss knife and can convert anything to -anything without any precautions thus requiring extra attention from a programmer. - -Besides, overflow checks proved to be useful for arithmetic operations, they can also be useful for +Overflow checks proved to be useful for arithmetic operations, they can also be useful for casts, but operator `as` doesn't perform such checks and just truncates (wraps) the results. The proposed new methods allow to separate potentially lossy conversions into intentionally wrapping casts and casts where truncation should be considered an error. -# Detailed design - -## Implement `Into`/`From` for integer types +`IntCast` and already implemented `Into`/`From` for lossless integer conversions are supposed to +replace most uses of operator `as` for conversions between integers. -Assume T and U are built-in integer types, then `Into` is implemented for `T` and `From` is -implemented for `U` iff the conversion from `T` to `U` is always lossless. -A good visualization (without `usize` and `isize`) can be found [here][1]. - -Implementations for `usize` and `isize` are platform dependent by design. If code is ported -to a new platform and some of `into()` conversions are not lossless anymore, they have to be -reviewed and replaced with checked casts. The porting effort shouldn't be large and potentially -caught mistakes can easily outweight it, for example, porting Rust codebase from 64-bit Windows to -32-bit Linux took minimal amount of time (see the Implementation section). +# Detailed design ## Introduce new trait `IntCast` into `std::num` (and `core::num`) @@ -64,26 +49,25 @@ highly specialized. conversion (this is needed for portability, some conversions can be lossless on one platform and potentially lossy on other). -## Make `usize` default type parameter for `Index` and `IndexMut` - -These traits don't currently have default type parameters and setting them will make type inference in index position possible in cases like: -``` -let a: u16 = 10; -let b = c[a.into()]; // With default type parameter -let b = c[a.into(): usize]; // Without default type parameter, but with type ascription, still more verbose than necessary -``` - ## Implementation An experiment implementing similar but somewhat different design and evaluating its practical impact is described [here][2]. +## Why `std` and not an external crate + +First, people will likely not bother depending on external crate for such a simple functionality and +will just use `as` instead, but using `as` is exactly what we would like to avoid. +Second, `IntCast` and similar arithmetic traits and methods should preferably go through +stabilization process together and have consistent interfaces. + # Drawbacks -This design is not fully ergonomic without type inference fallback based on default type parameters -(sometimes `into()` and `cast()` need redundant type hints) and without type ascription (there's no -way to give a type hint for the target type of `Into` inline). The first problem is currently resolved, -but the solution is feature-gated, removal of the feature-gate will reduce the need in type hints to -the minimum. The second problem will hopefully be resolved too in the near future. +`IntCast` is modeled after the trait `Into` and its design is not fully ergonomic without type +inference fallback based on default type parameters (sometimes `cast()` needs redundant type hints) +and without type ascription (there's no way to give a type hint for the target type of `Into` +inline). The first problem is currently resolved, but the solution is feature-gated, removal of the +feature-gate will reduce the need in type hints to the minimum. The second problem will hopefully be +resolved too in the near future. # Alternatives @@ -91,47 +75,20 @@ the minimum. The second problem will hopefully be resolved too in the near futur so short and convenient and doesn't require any imports and even works in constant expressions, and overflows never happen in code written by a reasonable programmer. -2. Use a separate trait for lossless conversions instead of `Into`, e.g. - - ``` - pub trait Widen: Sized { - fn widen(self) -> Target; - } - ``` - - It would still make sense to implement `Into` for lossless integer conversions, because they are -totally reasonable conversions and `Into` is by definition a trait for, well, reasonable -conversions. In this case a separate trait `Widen` would feel like a duplication. -The trait `Widen` will have to live in the prelude, like `Into`, otherwise it will be rarely -used, because the alternative (`as`) doesn't require importing anything (something similar already -happens with `ptr::null` vs `0 as *const T`). Adding new names to the prelude may be considered a -drawback. - -3. Core language solution for lossless conversions, e.g. new operator `as^` or `lossless_as` or -unary plus. This is much more intrusive and doesn't probably pull its weight. -It would still make sense to implement `Into` for lossless integer conversions, because they are -reasonable conversions. There's a non-zero chance that `Into` will get its own language sugar -somewhere in the remote future. - -4. Make lossless integer conversions implicit at language level. This alternative is not pursued. -In the relevant thread on internals many people spoke against this alternative and it had no -consensus. Moreover, originally the absence of these conversions is [by design][4] and not just an -omission. - -5. Methods `as()`/`wrapping_as()/...` may look better than `cast()`/`wrapping_cast()/...`, but `as` +2. Names `as()`/`wrapping_as()/...` may look better than `cast()`/`wrapping_cast()/...`, but `as` can't be used as a method name. Theoretically `as` can be made a context dependent keyword, then the names will become available. -6. `IntCast` can be splitted into several traits - `IntCast/WrappingIntCast/...`, but +3. `IntCast` can be splitted into several traits - `IntCast/WrappingIntCast/...`, but there's not much sense in multiplying entities - `IntCast` is ought to be implemented for a limited set of types and all its methods always go in group. -7. Diverge from arithmetic operations and always panic in `cast()`, not only with enabled assertions. +4. Diverge from arithmetic operations and always panic in `cast()`, not only with enabled assertions. This would make `cast()` equivalent to `checked_cast().unwrap()`. -8. Sign conversions with fixed target type described in [the experiment][2] are subsumed by `IntCast` in this -design, but they can probably be useful by themselves. They would also have to be provided in -several variants - `as_signed()/as_signed_wrapping()/...`. +5. Sign conversions with fixed target type described in [the experiment][2] are subsumed by `IntCast` +in this design, but they can probably be useful by themselves. They would also have to be provided +in several variants - `as_signed()/as_signed_wrapping()/...`. # Unresolved questions From 1af22ceed4ff478519df01154978b4dbe14d898d Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Wed, 24 Feb 2016 22:28:18 +0300 Subject: [PATCH 7/7] Use inherent methods instead of the trait --- text/0000-integer-conversions.md | 48 +++++++++++++++----------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/text/0000-integer-conversions.md b/text/0000-integer-conversions.md index 568500d1fe4..091c1035d20 100644 --- a/text/0000-integer-conversions.md +++ b/text/0000-integer-conversions.md @@ -5,33 +5,36 @@ # Summary -Implement new trait `IntCast` for checked and wrapping casts for potentially lossy integer conversions +Implement new methods for checked and wrapping casts for potentially lossy integer conversions analogous to checked and wrapping arithmetic operations. # Motivation Overflow checks proved to be useful for arithmetic operations, they can also be useful for -casts, but operator `as` doesn't perform such checks and just truncates (wraps) the results. +casts, but operator `as` doesn't perform such checks and just truncates/wraps the results. The proposed new methods allow to separate potentially lossy conversions into intentionally wrapping casts and casts where truncation should be considered an error. -`IntCast` and already implemented `Into`/`From` for lossless integer conversions are supposed to +These methods and already implemented `Into`/`From` for lossless integer conversions are supposed to replace most uses of operator `as` for conversions between integers. # Detailed design -## Introduce new trait `IntCast` into `std::num` (and `core::num`) +## Introduce new inherent methods for all the primitive integer types ``` -trait IntCast { - fn cast(self) -> Target; - fn wrapping_cast(self) -> Target; - fn checked_cast(self) -> Option; - fn overflowing_cast(self) -> (Target, bool); - fn saturating_cast(self) -> Target; +impl i32 { + fn cast(self) -> Target; + fn wrapping_cast(self) -> Target; + fn checked_cast(self) -> Option; + fn overflowing_cast(self) -> (Target, bool); + fn saturating_cast(self) -> Target; } ``` +`__UnspecifiedTrait` is a private unstable trait used as an implementation detail. It is guaranteed +that this trait is implemented for all the primitive integer types. + The methods correspond to existing methods for arithmetic operations like `add`/`wrapping_add`/ `checked_add`/`overflowing_add`/`saturating_add`. - `cast()` is equivalent to `as` but panics when the conversion is lossy and debug assertions are on. @@ -45,9 +48,9 @@ Statistically, `cast()` is the most common of these methods, `wrapping_cast()` i and usually related to hashes, random numbers or serialization, and the other methods are rare and highly specialized. -`IntCast` is implemented for all pairs of built-in integer types including pairs with lossless -conversion (this is needed for portability, some conversions can be lossless on one platform and -potentially lossy on other). +The conversion methods are implemented for all pairs of built-in integer types including pairs with +lossless conversions (this is required for portability, some conversions can be lossless on one +platforms and potentially lossy on others). ## Implementation An experiment implementing similar but somewhat different design and evaluating its practical @@ -55,19 +58,12 @@ impact is described [here][2]. ## Why `std` and not an external crate -First, people will likely not bother depending on external crate for such a simple functionality and +People will likely not bother depending on external crate for such a simple functionality and will just use `as` instead, but using `as` is exactly what we would like to avoid. -Second, `IntCast` and similar arithmetic traits and methods should preferably go through -stabilization process together and have consistent interfaces. # Drawbacks -`IntCast` is modeled after the trait `Into` and its design is not fully ergonomic without type -inference fallback based on default type parameters (sometimes `cast()` needs redundant type hints) -and without type ascription (there's no way to give a type hint for the target type of `Into` -inline). The first problem is currently resolved, but the solution is feature-gated, removal of the -feature-gate will reduce the need in type hints to the minimum. The second problem will hopefully be -resolved too in the near future. +None. # Alternatives @@ -79,9 +75,9 @@ and overflows never happen in code written by a reasonable programmer. can't be used as a method name. Theoretically `as` can be made a context dependent keyword, then the names will become available. -3. `IntCast` can be splitted into several traits - `IntCast/WrappingIntCast/...`, but -there's not much sense in multiplying entities - `IntCast` is ought to be implemented for a -limited set of types and all its methods always go in group. +3. Use methods of a trait `IntCast` instead of inherent methods. The library team is unwilling to +expose numeric traits from the standard library even if they are unstable and brought in scope by +the prelude. 4. Diverge from arithmetic operations and always panic in `cast()`, not only with enabled assertions. This would make `cast()` equivalent to `checked_cast().unwrap()`. @@ -92,7 +88,7 @@ in several variants - `as_signed()/as_signed_wrapping()/...`. # Unresolved questions -None so far +None. [1]: https://internals.rust-lang.org/t/implicit-widening-polymorphic-indexing-and-similar-ideas/1141/45 [2]: https://internals.rust-lang.org/t/implicit-widening-polymorphic-indexing-and-similar-ideas/1141/70