From 8b147cdb619dc9cdce9ce7c906966e96420a82fb Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 21 Sep 2021 17:53:14 -0700 Subject: [PATCH 01/17] Filling out template with PR 845 --- proposals/README.md | 1 + proposals/p0845.md | 62 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 proposals/p0845.md diff --git a/proposals/README.md b/proposals/README.md index 9d5b250dc5ef..b8e19a90b9c0 100644 --- a/proposals/README.md +++ b/proposals/README.md @@ -73,5 +73,6 @@ request: - [0777 - Inheritance](p0777.md) - [0820 - Implicit conversions](p0820.md) - [0829 - One way principle](p0829.md) +- [0845 - as expressions](p0845.md) diff --git a/proposals/p0845.md b/proposals/p0845.md new file mode 100644 index 000000000000..4caee2233711 --- /dev/null +++ b/proposals/p0845.md @@ -0,0 +1,62 @@ +# as expressions + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/845) + + + +## Table of contents + +- [Problem](#problem) +- [Background](#background) +- [Proposal](#proposal) +- [Details](#details) +- [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals) +- [Alternatives considered](#alternatives-considered) + + + +## Problem + +TODO: What problem are you trying to solve? How important is that problem? Who +is impacted by it? + +## Background + +TODO: Is there any background that readers should consider to fully understand +this problem and your approach to solving it? + +## Proposal + +TODO: Briefly and at a high level, how do you propose to solve the problem? Why +will that in fact solve it? + +## Details + +TODO: Fully explain the details of the proposed solution. + +## Rationale based on Carbon's goals + +TODO: How does this proposal effectively advance Carbon's goals? Rather than +re-stating the full motivation, this should connect that motivation back to +Carbon's stated goals for the project or language. This may evolve during +review. Use links to appropriate goals, for example: + +- [Community and culture](/docs/project/goals.md#community-and-culture) +- [Language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem) +- [Performance-critical software](/docs/project/goals.md#performance-critical-software) +- [Software and language evolution](/docs/project/goals.md#software-and-language-evolution) +- [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write) +- [Practical safety and testing mechanisms](/docs/project/goals.md#practical-safety-and-testing-mechanisms) +- [Fast and scalable development](/docs/project/goals.md#fast-and-scalable-development) +- [Modern OS platforms, hardware architectures, and environments](/docs/project/goals.md#modern-os-platforms-hardware-architectures-and-environments) +- [Interoperability with and migration from existing C++ code](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code) + +## Alternatives considered + +TODO: What alternative solutions have you considered? From f78fdc840750fea632aba422600c9dfb2dd8ba9d Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 21 Sep 2021 18:56:31 -0700 Subject: [PATCH 02/17] Design updates for `as` expressions. --- docs/design/expressions/README.md | 7 +- docs/design/expressions/as_expressions.md | 147 ++++++++++++++++++ .../expressions/implicit_conversions.md | 27 ++-- docs/design/generics/details.md | 2 +- proposals/README.md | 2 +- proposals/p0845.md | 19 +-- 6 files changed, 175 insertions(+), 29 deletions(-) create mode 100644 docs/design/expressions/as_expressions.md diff --git a/docs/design/expressions/README.md b/docs/design/expressions/README.md index 2eb454590df9..9654c3e1a5c4 100644 --- a/docs/design/expressions/README.md +++ b/docs/design/expressions/README.md @@ -11,7 +11,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Table of contents - [Overview](#overview) -- [Implicit conversions](#implicit-conversions) +- [Conversions and casts](#conversions-and-casts) @@ -29,12 +29,15 @@ fn Foo(a: i32*) -> i32 { Here, the parameter type `i32*`, the return type `i32`, and the operand `*a` of the `return` statement are all expressions. -## Implicit conversions +## Conversions and casts When an expression appears in a context in which an expression of a specific type is expected, [implicit conversions](implicit_conversions.md) are applied to convert the expression to the target type. +Expressions can also be converted to a specific type using an +[`as` expression](as_expressions.md). + ``` fn Bar(n: i32); fn Baz(n: i64) { diff --git a/docs/design/expressions/as_expressions.md b/docs/design/expressions/as_expressions.md new file mode 100644 index 000000000000..89e5d8985826 --- /dev/null +++ b/docs/design/expressions/as_expressions.md @@ -0,0 +1,147 @@ +# `as` expressions + + + + + +## Table of contents + +- [Overview](#overview) +- [Built-in types](#built-in-types) + - [Data types](#data-types) + - [Compatible types](#compatible-types) + - [Pointer conversions](#pointer-conversions) +- [Extensibility](#extensibility) +- [Alternatives considered](#alternatives-considered) +- [References](#references) + + + +## Overview + +An expression of one type can be explicitly cast to another type by using an +`as` expression: + +``` +var n: i32 = Get(); +var f: f32 = n as f32; +``` + +An `as` expression can be used to perform any implicit conversion, either when +the context does not imply a destination type or when it is valuable to a reader +of the code to make the conversion explicit. In addition, `as` expressions can +perform safe conversions that nonetheless should not be performed implicitly, +such as lossy conversions or conversions that lose capabilities or change the +way a type would be interpreted. + +As guidelines, an `as` conversion should be permitted when: + +- The conversion is _safe_: it produces a well-defined output value for each + input value. +- The conversion is _unsurprising_: the resulting value is the expected value + in the destination type. + +In cases where a cast is only defined for a subset of the possible inputs, an +`unsafe_as` expression can be used. An `unsafe_as` expression behaves like an +`as` expression, except that the domain of the conversion is narrower than the +entire input type, so the conversion is not safe as defined above. + +It is possible for user-defined types to [extend](#extensibility) the set of +valid explicit casts that can be performed by `as` and `unsafe_as`. Such +extensions are expected to follow these guidelines. + +## Built-in types + +### Data types + +In addition to the [implicit conversions](implicit_conversions.md#data-types), +the following numeric conversion is supported by `as`: + +- `iN`, `uN`, or `fN` -> `fM`, for any `N` and `M`. Values that cannot be + exactly represented are suitably rounded to one of the two nearest + representable values. Very large finite values may be rounded to an + infinity. NaN values are converted to NaN values. + +- `iN` or `uN` -> `bool`. Zero converts to `false` and non-zero values convert + to `true`. + +- `bool` -> `iN` or `uN`. `false` converts to `0` and `true` converts to `1`. + +The following additional numeric conversions are supported by `unsafe_as`: + +- `iN` or `uN` -> `iM` or `uM`, for any `N` and `M`. It is a programming error + if the source value cannot be represented in the destination type. + + **TODO:** Once we have a two's complement truncation operation with defined + behavior on overflow, link to it from here as an alternative. + +- `fN` -> `iM`, for any `N` and `M`. Values that cannot be exactly represented + are suitably rounded to one of the two nearest representable values. It is a + programming error if the source value does not round to an integer that can + be represented in the destination type. + +**Note:** The precise rounding rules for these conversions have not yet been +decided. + +### Compatible types + +The following conversion is supported by `as`: + +- `T` -> `U` if `T` is + [cmopatible](../generics/terminology.md#compatible-types) with `U`. + +**Future work:** We may need a mechanism to restrict which conversions between +adapters are permitted and which code can perform them. Some of the conversions +permitted by this rule may only be allowed in certain contexts. + +### Pointer conversions + +The following pointer conversion is supported by `unsafe_as`: + +- `T*` -> `U*` if `U` is a subtype of `T`. + +This cast converts in the opposite direction to the corresponding +[implicit conversion](implicit_conversions.md#pointer-conversions). It is a +programming error if the source pointer does not point to a `U` object. + +**Note:** `unsafe_as` cannot convert between unrelated pointer types, because +there are no input values for which the conversion would produce a well-defined +output value. Separate facilities will be provided for reinterpreting memory as +a distinct type. + +## Extensibility + +Explicit casts can be defined for user-defined types such as +[classes](../classes.md) by implementing the `As` or `UnsafeAs` interface: + +``` +interface UnsafeAs(Dest:! Type) { + fn Convert[me: Self]() -> Dest; +} +interface As(Dest:! Type) extends UnsafeAs(Dest) { + // Inherited from UnsafeAs(Dest): + // fn Convert[me: Self]() -> Dest; +} +``` + +The expression `x as U` is rewritten to `x.(As(U).Convert)()`. The expression +`x unsafe_as U` is rewritten to `x.(UnsafeAs(U).Convert)()`. + +**Future work:** Add a `TryAs` interface to test whether an input value is in +the domain of the conversion and convert it if so. + +## Alternatives considered + +- [Do not distinguish between safe and unsafe casts](/docs/proposals/p0845.md#no-unsafe_as) +- [Do not distinguish between safe as and implicit conversions](/docs/proposals/p0845.md#as-only-performs-implicit-conversions) +- [Allow `unsafe_as` to perform bit casts](/docs/proposals/p0845.md#unsafe_as-performs-bit-casts) + +## References + +- [Implicit conversions in C++](https://en.cppreference.com/w/cpp/language/implicit_conversion) +- Proposal + [#845: `as` expressions](https://github.com/carbon-language/carbon-lang/pull/845). diff --git a/docs/design/expressions/implicit_conversions.md b/docs/design/expressions/implicit_conversions.md index 3d003ae8fd9b..a049858e4920 100644 --- a/docs/design/expressions/implicit_conversions.md +++ b/docs/design/expressions/implicit_conversions.md @@ -248,34 +248,29 @@ of `TT2`. ## Semantics An implicit conversion of an expression `E` of type `T` to type `U`, when -permitted, always has the same meaning as the explicit cast expression `E as U`. -Moreover, such an implicit conversion is expected to exactly preserve the value. -For example, `(E as U) as T`, if valid, should be expected to result in the same -value as produced by `E`. - -**Note:** The explicit cast expression syntax has not yet been decided. The use -of `E as T` in this document is provisional. +permitted, always has the same meaning as the +[explicit cast expression `E as U`](as_expressions.md). Moreover, such an +implicit conversion is expected to exactly preserve the value. For example, +`(E as U) as T`, if valid, should be expected to result in the same value as +produced by `E`. ## Extensibility Implicit conversions can be defined for user-defined types such as -[classes](../classes.md) by implementing the `ImplicitAs` interface: +[classes](../classes.md) by implementing the `ImplicitAs` interface, which +extends +[the `As` interface used to implement `as` expressions](as_expressions.md#extensibility): ``` -interface As(Dest:! Type) { - fn Convert[me: Self]() -> Dest; +interface ImplicitAs(Dest:! Type) extends As(Dest) { + // Inherited from As(Dest): + // fn Convert[me: Self]() -> Dest; } -interface ImplicitAs(Dest:! Type) extends As(Dest) {} ``` When attempting to implicitly convert an expression `x` to type `U`, the expression is rewritten to `x.(ImplicitAs(U).Convert)()`. -**Note:** The `As` interface is intended to be used as the implementation -vehicle for explicit casts: `x as U` would be rewritten as -`x.(As(U).Convert)()`. However, the explicit cast expression syntax has not yet -been decided, so this rewrite is provisional. - Note that implicit conversions are not transitive. Even if an `impl A as ImplicitAs(B)` and an `impl B as ImplicitAs(C)` are both provided, an expression of type `A` cannot be implicitly converted to type `C`. Allowing diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index ccd829f9a4f1..d08dffa27c0b 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -1572,7 +1572,7 @@ addition to using the same data representation, they both implement one interface, `Hashable`, and use the same implementation for that interface. The one difference between them is that `Song as Hashable` may be implicitly converted to `Song`, which implements interface `Printable`, and -`PlayableSong as Hashable` may be implicilty converted to `PlayableSong`, which +`PlayableSong as Hashable` may be implicitly converted to `PlayableSong`, which implements interface `Media`. This means that it is safe to convert between `HashMap(Song, Int) == HashMap(Song as Hashable, Int)` and `HashMap(PlayableSong, Int) == HashMap(PlayableSong as Hashable, Int)` (though diff --git a/proposals/README.md b/proposals/README.md index b8e19a90b9c0..7ff7e28609c4 100644 --- a/proposals/README.md +++ b/proposals/README.md @@ -73,6 +73,6 @@ request: - [0777 - Inheritance](p0777.md) - [0820 - Implicit conversions](p0820.md) - [0829 - One way principle](p0829.md) -- [0845 - as expressions](p0845.md) +- [0845 - `as` expressions](p0845.md) diff --git a/proposals/p0845.md b/proposals/p0845.md index 4caee2233711..2c67d814bfdb 100644 --- a/proposals/p0845.md +++ b/proposals/p0845.md @@ -1,4 +1,4 @@ -# as expressions +# `as` expressions @@ -33,12 +35,7 @@ this problem and your approach to solving it? ## Proposal -TODO: Briefly and at a high level, how do you propose to solve the problem? Why -will that in fact solve it? - -## Details - -TODO: Fully explain the details of the proposed solution. +See changes to the design. ## Rationale based on Carbon's goals @@ -59,4 +56,8 @@ review. Use links to appropriate goals, for example: ## Alternatives considered -TODO: What alternative solutions have you considered? +### No `unsafe_as` + +### `as` only performs implicit conversions + +### `unsafe_as` performs bit casts From 7889ec1c22e4e963f00ea59e166afb77d0b9b0ca Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 21 Sep 2021 19:25:12 -0700 Subject: [PATCH 03/17] Add a keyword list including `as` and `unsafe_as`. --- docs/design/lexical_conventions/words.md | 47 ++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/docs/design/lexical_conventions/words.md b/docs/design/lexical_conventions/words.md index 04a8490a2ae6..2c432e6f29b9 100644 --- a/docs/design/lexical_conventions/words.md +++ b/docs/design/lexical_conventions/words.md @@ -11,6 +11,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Table of contents - [Overview](#overview) +- [Keywords](#keywords) - [Alternatives considered](#alternatives-considered) - [References](#references) @@ -27,6 +28,52 @@ follow lexical conventions for identifiers based on the precise rules are decided; see the [Unicode source files](/proposals/p0142.md#characters-in-identifiers) proposal. +## Keywords + +The following words are interpreted as keywords: + +- `abstract` +- `addr` +- `alias` +- `and` +- `api` +- `as` +- `auto` +- `base` +- `break` +- `case` +- `class` +- `continue` +- `default` +- `else` +- `extends` +- `external` +- `fn` +- `for` +- `friend` +- `if` +- `impl` +- `import` +- `interface` +- `let` +- `library` +- `match` +- `namespace` +- `not` +- `or` +- `override` +- `package` +- `partial` +- `private` +- `protected` +- `return` +- `returned` +- `unsafe_as` +- `var` +- `virtual` +- `where` +- `while` + ## Alternatives considered - [Character encoding: We could restrict words to ASCII.](/proposals/p0142.md#character-encoding-1) From 6a54aeb4e6b118120df684845853cadd9f4180ff Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 22 Sep 2021 14:51:08 -0700 Subject: [PATCH 04/17] Remove speculative future work and fix typo. --- docs/design/expressions/as_expressions.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/design/expressions/as_expressions.md b/docs/design/expressions/as_expressions.md index 89e5d8985826..41d4ace20a2b 100644 --- a/docs/design/expressions/as_expressions.md +++ b/docs/design/expressions/as_expressions.md @@ -92,7 +92,7 @@ decided. The following conversion is supported by `as`: - `T` -> `U` if `T` is - [cmopatible](../generics/terminology.md#compatible-types) with `U`. + [compatible](../generics/terminology.md#compatible-types) with `U`. **Future work:** We may need a mechanism to restrict which conversions between adapters are permitted and which code can perform them. Some of the conversions @@ -131,9 +131,6 @@ interface As(Dest:! Type) extends UnsafeAs(Dest) { The expression `x as U` is rewritten to `x.(As(U).Convert)()`. The expression `x unsafe_as U` is rewritten to `x.(UnsafeAs(U).Convert)()`. -**Future work:** Add a `TryAs` interface to test whether an input value is in -the domain of the conversion and convert it if so. - ## Alternatives considered - [Do not distinguish between safe and unsafe casts](/docs/proposals/p0845.md#no-unsafe_as) From 56d467907f4757879cf49f0642ecc32bc116e35d Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 22 Sep 2021 15:04:13 -0700 Subject: [PATCH 05/17] Describe the `as` / implicit conversion rule as a consistency rule rather than suggesting it's the semantic foundation of implicit conversions. The lossless + semantics-preserving rules should be sufficient to identify what an implicit conversion does. --- docs/design/expressions/implicit_conversions.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/design/expressions/implicit_conversions.md b/docs/design/expressions/implicit_conversions.md index a049858e4920..de106937783d 100644 --- a/docs/design/expressions/implicit_conversions.md +++ b/docs/design/expressions/implicit_conversions.md @@ -21,7 +21,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Pointer conversions](#pointer-conversions) - [Pointer conversion examples](#pointer-conversion-examples) - [Type-of-types](#type-of-types) -- [Semantics](#semantics) +- [Consistency with `as`](#consistency-with-as) - [Extensibility](#extensibility) - [Alternatives considered](#alternatives-considered) - [References](#references) @@ -245,14 +245,15 @@ can be implicitly converted to the type-of-type `TT2` if `T` [satisfies the requirements](../generics/details.md#subtyping-between-type-of-types) of `TT2`. -## Semantics +## Consistency with `as` An implicit conversion of an expression `E` of type `T` to type `U`, when permitted, always has the same meaning as the -[explicit cast expression `E as U`](as_expressions.md). Moreover, such an -implicit conversion is expected to exactly preserve the value. For example, +[explicit cast expression `E as U`](as_expressions.md). Moreover, because such +an implicit conversion is expected to exactly preserve the value, `(E as U) as T`, if valid, should be expected to result in the same value as -produced by `E`. +produced by `E` even if the `as T` cast cannot be performed as an implicit +conversion. ## Extensibility From 6dd10c0181144e95e785272ba551f5c0104ad610 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 24 Sep 2021 16:39:36 -0700 Subject: [PATCH 06/17] Finish filling out proposal. unsafe_as -> assume_as. --- docs/design/expressions/as_expressions.md | 24 +-- proposals/p0845.md | 222 ++++++++++++++++++++-- 2 files changed, 220 insertions(+), 26 deletions(-) diff --git a/docs/design/expressions/as_expressions.md b/docs/design/expressions/as_expressions.md index 41d4ace20a2b..f6bbd6393a4e 100644 --- a/docs/design/expressions/as_expressions.md +++ b/docs/design/expressions/as_expressions.md @@ -46,12 +46,12 @@ As guidelines, an `as` conversion should be permitted when: in the destination type. In cases where a cast is only defined for a subset of the possible inputs, an -`unsafe_as` expression can be used. An `unsafe_as` expression behaves like an +`assume_as` expression can be used. An `assume_as` expression behaves like an `as` expression, except that the domain of the conversion is narrower than the entire input type, so the conversion is not safe as defined above. It is possible for user-defined types to [extend](#extensibility) the set of -valid explicit casts that can be performed by `as` and `unsafe_as`. Such +valid explicit casts that can be performed by `as` and `assume_as`. Such extensions are expected to follow these guidelines. ## Built-in types @@ -71,7 +71,7 @@ the following numeric conversion is supported by `as`: - `bool` -> `iN` or `uN`. `false` converts to `0` and `true` converts to `1`. -The following additional numeric conversions are supported by `unsafe_as`: +The following additional numeric conversions are supported by `assume_as`: - `iN` or `uN` -> `iM` or `uM`, for any `N` and `M`. It is a programming error if the source value cannot be represented in the destination type. @@ -100,7 +100,7 @@ permitted by this rule may only be allowed in certain contexts. ### Pointer conversions -The following pointer conversion is supported by `unsafe_as`: +The following pointer conversion is supported by `assume_as`: - `T*` -> `U*` if `U` is a subtype of `T`. @@ -108,7 +108,7 @@ This cast converts in the opposite direction to the corresponding [implicit conversion](implicit_conversions.md#pointer-conversions). It is a programming error if the source pointer does not point to a `U` object. -**Note:** `unsafe_as` cannot convert between unrelated pointer types, because +**Note:** `assume_as` cannot convert between unrelated pointer types, because there are no input values for which the conversion would produce a well-defined output value. Separate facilities will be provided for reinterpreting memory as a distinct type. @@ -116,26 +116,26 @@ a distinct type. ## Extensibility Explicit casts can be defined for user-defined types such as -[classes](../classes.md) by implementing the `As` or `UnsafeAs` interface: +[classes](../classes.md) by implementing the `As` or `AssumeAs` interface: ``` -interface UnsafeAs(Dest:! Type) { +interface AssumeAs(Dest:! Type) { fn Convert[me: Self]() -> Dest; } -interface As(Dest:! Type) extends UnsafeAs(Dest) { - // Inherited from UnsafeAs(Dest): +interface As(Dest:! Type) extends AssumeAs(Dest) { + // Inherited from AssumeAs(Dest): // fn Convert[me: Self]() -> Dest; } ``` The expression `x as U` is rewritten to `x.(As(U).Convert)()`. The expression -`x unsafe_as U` is rewritten to `x.(UnsafeAs(U).Convert)()`. +`x assume_as U` is rewritten to `x.(AssumeAs(U).Convert)()`. ## Alternatives considered -- [Do not distinguish between safe and unsafe casts](/docs/proposals/p0845.md#no-unsafe_as) +- [Do not distinguish between safe and unsafe casts](/docs/proposals/p0845.md#merge-as-and-assume_as) - [Do not distinguish between safe as and implicit conversions](/docs/proposals/p0845.md#as-only-performs-implicit-conversions) -- [Allow `unsafe_as` to perform bit casts](/docs/proposals/p0845.md#unsafe_as-performs-bit-casts) +- [Use a different name for `assume_as`](/docs/proposals/p0845.md#different-name-for-assume_as) ## References diff --git a/proposals/p0845.md b/proposals/p0845.md index 2c67d814bfdb..51b4175be44e 100644 --- a/proposals/p0845.md +++ b/proposals/p0845.md @@ -17,32 +17,166 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Proposal](#proposal) - [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals) - [Alternatives considered](#alternatives-considered) - - [No `unsafe_as`](#no-unsafe_as) + - [Merge `as` and `assume_as`](#merge-as-and-assume_as) - [`as` only performs implicit conversions](#as-only-performs-implicit-conversions) - - [`unsafe_as` performs bit casts](#unsafe_as-performs-bit-casts) + - [Different name for `assume_as`](#different-name-for-assume_as) ## Problem -TODO: What problem are you trying to solve? How important is that problem? Who -is impacted by it? +We would like to provide a notation for the following operations: + +- Requesting a type conversion in order to select an operation to perform, or + to resolve an ambiguity between possible operations: + ``` + fn Ratio(a: i32, b: i32) -> f64 { + // Note that a / b would invoke a different / operation. + return a / (b as f64); + } + ``` +- Requesting a type conversion to a narrower type, where the developer + believes the value is a value of the narrower type: + ``` + fn Extract(p: Base*) -> i32 { + if (p->kind == DerivedKind) { + // I promise that p points to a Derived. + return (p as Derived*)->value; + } + return -1; + } + fn ByteEncode(n: i32) -> Optional(i8) { + if (n >= i8.MinValue and n <= i8.MaxValue) { + // I promise that n fits in i8. + return .Some(n as i8); + } + return .None; + } + ``` +- Specifying the type that an expression will have or will be converted into, + for documentation purposes. + ``` + class Thing { + var id: i32; + } + fn PrintThing(t: Thing) { + // 'as i32' reminds the reader what type we're printing. + Print(t.id as i32); + } + ``` +- Specifying the type that an expression is expected to have, potentially + after implicit conversions, as a form of static assertion. + ``` + fn Munge() { + // I expect this expression to produce a Widget but I'm getting compiler + // errors and I'd like to narrow down why. + F(Some().Complex().Expression() as Widget); + } + ``` + +In general, the developer wants to specify that an expression should be +considered to produce a value of a particular type, and that type might be more +general than the type of the expression, the same as the type of the expression, +more specific than the type of the expression, or perhaps might represent a +different way of viewing the value. + +The first of the above problems is especially important in Carbon due to the use +of facet types for generics. Explicit conversions of types to interfaces will be +necessary in order to select the meaning of operations, because the same member +name on different facet types for the same underlying type will in general have +different meanings. + +For this proposal, the following are out of scope: + +- Requesting a type conversion that changes the value, such as by truncation. +- Determining whether a value can be represented in a narrower type. ## Background -TODO: Is there any background that readers should consider to fully understand -this problem and your approach to solving it? +C++ provides a collection of different kinds of casts and conversions from an +expression `x` to a type `T`: + +- Copy-initialization: `T v = x;` +- Direct-initialization: `T v(x);` +- Named casts: + - `static_cast(x)` + - `const_cast(x)` + - `reinterpret_cast(x)` + - `dynamic_cast(x)` +- C-style casts: `T(x)` or equivalently `(T)x` + - These can do anything that `static_cast`, `const_cast`, and + `reinterpret_cast` can do, but ignore access control on base classes. +- List-initialization: `T{x}` + - This can do anything that implicit conversion can do, and can also + initialize a single -- real or notional -- subobject of `T`. + - Narrowing conversions are disallowed. + +These conversions are all different, and each of them has some surprising or +unsafe behavior. + +Swift provides four forms of type casting operation: + +- `x as T` performs a conversion from subtype to supertype. +- `x as! T` performs a conversion from supertype to subtype, with the + assumption that the value inhabits the subtype. +- `x as? T` performs a conversion from supertype to subtype, producing an + `Optional`. +- `T(x)` and similar construction expressions are used to convert between + types without a subtyping relationship, such as between integer and + floating-point types. + +In Swift, `x as T` is always unsurprising and safe. + +Rust provides the following: + +- `x as T` performs a conversion to type `T`. + - When there is no corresponding value, some specified value is produced: + this conversion will perform 2's complement truncation on integers and + will saturate when converting large floating-point values to integers. + - Conversions between distinct pointer types, and between pointers and + integers, are permitted. Rust treats accesses through pointers as + unsafe, but not pointer arithmetic or casting. + +This cast can perform some conversions with surprising results, such as integer +truncation. It can also have surprising performance implications, because it +defines the behavior of converting an out-of-range value -- for example, when +converting from floating-point to integer -- in ways that aren't supported +across all modern targets. + +Haskell and Scala support type ascription notation, `x : T`. This has also been +proposed for Rust. This notation constrains the type checker to find a type for +the expression `x` that is consistent with `T`, and is used: + +- for documentation purposes, +- to guide the type checker to select a particular meaning of the code in the + presence of ambiguity, and +- as a diagnostic tool when attempting to understand type inference failures. ## Proposal -See changes to the design. +Carbon provides two explicit cast operations: -## Rationale based on Carbon's goals +- `x as T` performs an unsurprising and safe conversion from `x` to type `T`. + - This can be used to perform any implicit conversion explicitly. As in + Swift, this can therefore be used to convert from subtype to supertype. + - This can also be used to perform an unsurprising and safe conversion + that cannot be performed implicitly because it's lossy, such as from + `i32` to `f32`. +- `x assume_as T` performs an unsurprising conversion from `x` to `T`, in the + case where it is not necessarily statically known that `x` can be + represented in type `T`. The value of `x` is assumed to be representable in + type `T`, and the case where `x` cannot be represented in type `T` is a + programming error. + - As in Swift, this can be used to convert from supertype to subtype. + - This can also be used to perform a conversion that is unsurprising and + safe for some subset of the possible values of type `x`, such as from + `f32` to `i32`. + - Any conversion that can be performed by `as` can also be performed by + `assume_as`. + +See changes to the design for details. -TODO: How does this proposal effectively advance Carbon's goals? Rather than -re-stating the full motivation, this should connect that motivation back to -Carbon's stated goals for the project or language. This may evolve during -review. Use links to appropriate goals, for example: +## Rationale based on Carbon's goals - [Community and culture](/docs/project/goals.md#community-and-culture) - [Language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem) @@ -56,8 +190,68 @@ review. Use links to appropriate goals, for example: ## Alternatives considered -### No `unsafe_as` +### Merge `as` and `assume_as` + +We could provide a single type-casting operator that can perform more of the +conversions supported by `assume_as`. The remaining conversions could be +relegated to some other syntax. + +One particularly appealing option would be to permit `as` to convert freely +between integer and floating-point types, but not permit it to convert from +supertype to subtype. + +Advantage: + +- Developers many not want to be reminded about the possibility of overflow in + conversions to integer types. +- This would make `as` more consistent with arithmetic operations, which will + likely have no overt indication that they're unsafe in the presence of + integer overflow. + +Disadvantage: + +- No clear foundation for which conversions can be performed by `as` and which + cannot. +- An `as` expression would be less suitable for selecting which operation to + perform if it can be unsafe. +- Under maintenance, every usage of `as` would need additional scrutiny + because it's not in general a safe operation. ### `as` only performs implicit conversions -### `unsafe_as` performs bit casts +We could limit `as` to performing only implicit conversions. This would mean +that `as` cannot perform lossy conversions. + +Advantage: + +- One fewer set of rules for developers to be aware of. + +Disadvantage: + +- Converting between integer and floating-point types is common, and providing + built-in syntax for it seems valuable. + +### Different name for `assume_as` + +We could use a different name for `assume_as`, such as `unsafe_as` or `as!`. + +Advantage of `as!`: + +- Terser. + +Disadvantage of `as!`: + +- Including a `!` as a suffix on a word-shaped operator would be novel and + risks making the lexing rules less uniform. +- We are planning for the `!` punctuation symbol to be used to indicate a + compile-time operation, such as a generic or template parameter, or a + metaprogramming operation. + +Advantage of `unsafe_as`: + +- Clearer that violation of the assumption is a programming error. + +Disadvantage of `unsafe_as`: + +- Less clear about what aspect of the conversion is unsafe, and that the + conversion is safe if the input value is suitable. From 81a47c3f3c687799010a1fd4350bc02ea99987e4 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 1 Oct 2021 15:42:33 -0700 Subject: [PATCH 07/17] Apply suggestions from code review Co-authored-by: Geoff Romer --- proposals/p0845.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/p0845.md b/proposals/p0845.md index 51b4175be44e..24d986e19e89 100644 --- a/proposals/p0845.md +++ b/proposals/p0845.md @@ -41,14 +41,14 @@ We would like to provide a notation for the following operations: fn Extract(p: Base*) -> i32 { if (p->kind == DerivedKind) { // I promise that p points to a Derived. - return (p as Derived*)->value; + return (p assume_as Derived*)->value; } return -1; } fn ByteEncode(n: i32) -> Optional(i8) { if (n >= i8.MinValue and n <= i8.MaxValue) { // I promise that n fits in i8. - return .Some(n as i8); + return .Some(n assume_as i8); } return .None; } From f894751491269773ed8722b460191c4d46adeb82 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 1 Oct 2021 15:42:42 -0700 Subject: [PATCH 08/17] Responses to review comments. --- docs/design/expressions/as_expressions.md | 11 ++- docs/design/lexical_conventions/words.md | 2 +- proposals/p0845.md | 109 ++++++++++++++++++++++ 3 files changed, 117 insertions(+), 5 deletions(-) diff --git a/docs/design/expressions/as_expressions.md b/docs/design/expressions/as_expressions.md index f6bbd6393a4e..343eba5952df 100644 --- a/docs/design/expressions/as_expressions.md +++ b/docs/design/expressions/as_expressions.md @@ -66,10 +66,8 @@ the following numeric conversion is supported by `as`: representable values. Very large finite values may be rounded to an infinity. NaN values are converted to NaN values. -- `iN` or `uN` -> `bool`. Zero converts to `false` and non-zero values convert - to `true`. - -- `bool` -> `iN` or `uN`. `false` converts to `0` and `true` converts to `1`. +- `bool` -> `iN` or `uN`. `false` converts to `0` and `true` converts to `1` + (or to `-1` for `i1`). The following additional numeric conversions are supported by `assume_as`: @@ -84,6 +82,9 @@ The following additional numeric conversions are supported by `assume_as`: programming error if the source value does not round to an integer that can be represented in the destination type. +Conversions from numeric types to `bool` are not supported with `as`; instead of +using `as bool`, such conversions can be performed with `!= 0`. + **Note:** The precise rounding rules for these conversions have not yet been decided. @@ -136,6 +137,8 @@ The expression `x as U` is rewritten to `x.(As(U).Convert)()`. The expression - [Do not distinguish between safe and unsafe casts](/docs/proposals/p0845.md#merge-as-and-assume_as) - [Do not distinguish between safe as and implicit conversions](/docs/proposals/p0845.md#as-only-performs-implicit-conversions) - [Use a different name for `assume_as`](/docs/proposals/p0845.md#different-name-for-assume_as) +- [Allow `iN as bool`](/docs/proposals/p0845.md#integer-to-bool-conversions) +- [Allow `bool as iN`](/docs/proposals/p0845.md#bool-to-integer-conversions) ## References diff --git a/docs/design/lexical_conventions/words.md b/docs/design/lexical_conventions/words.md index 2c432e6f29b9..c97f31d8a91f 100644 --- a/docs/design/lexical_conventions/words.md +++ b/docs/design/lexical_conventions/words.md @@ -38,6 +38,7 @@ The following words are interpreted as keywords: - `and` - `api` - `as` +- `assume_as` - `auto` - `base` - `break` @@ -68,7 +69,6 @@ The following words are interpreted as keywords: - `protected` - `return` - `returned` -- `unsafe_as` - `var` - `virtual` - `where` diff --git a/proposals/p0845.md b/proposals/p0845.md index 24d986e19e89..9af889c3bd53 100644 --- a/proposals/p0845.md +++ b/proposals/p0845.md @@ -20,6 +20,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Merge `as` and `assume_as`](#merge-as-and-assume_as) - [`as` only performs implicit conversions](#as-only-performs-implicit-conversions) - [Different name for `assume_as`](#different-name-for-assume_as) + - [Integer to bool conversions](#integer-to-bool-conversions) + - [Bool to integer conversions](#bool-to-integer-conversions) @@ -117,6 +119,10 @@ unsafe behavior. Swift provides four forms of type casting operation: - `x as T` performs a conversion from subtype to supertype. + - `pattern as T` in a pattern matching context converts a pattern that + matches a subtype to a pattern that matches a supertype, by performing a + runtime type test. This effectively results in a checked supertype to + subtype conversion. - `x as! T` performs a conversion from supertype to subtype, with the assumption that the value inhabits the subtype. - `x as? T` performs a conversion from supertype to subtype, producing an @@ -255,3 +261,106 @@ Disadvantage of `unsafe_as`: - Less clear about what aspect of the conversion is unsafe, and that the conversion is safe if the input value is suitable. + +Advantage of `assert_as`: + +- Term "assert" may be more familiar than "assume". + +Disadvantage of `assert_as`: + +- The emphasis of "assert" is on checking, and it's not clear whether we will + implement checks for all the kinds of programming error that can result from + `assume_as`, in particular for pointer downcasts. Even if such checks are + implemented, they may not be enabled in a normal debugging build. +- An assertion failure is usually defined to either produce a trap with a + diagnostic message or to allow the program to continue running and + potentially handle the situation in a different way. An assumption failure + is usually defined in a way that permits anything to happen, so that we can + optimize on the basis that the situation is not reached. The latter is the + intended semantic model for `assume_as`. + +### Integer to bool conversions + +We could allow a conversion of integer types (and perhaps even floating-point +types) to `bool`, converting non-zero values to `true` and converting zeroes to +`false`. + +Advantage: + +- This treatment of non-zero values as being "truthy" and zero values as being + "falsy" is familiar to developers of various other languages. +- Uniform treatment of types that can be notionally converted to a Boolean + value may be useful in templates and generics in some cases. + +Disadvantage: + +- The treatment of all non-zero values as being "truthy" is somewhat arbitrary + and can be confusing. +- Such conversions are a known source of bugs, especially when performed + implicitly. `as` conversions will likely be fairly common and routine in + Carbon code due to their use in generics. As such, they may be written + without much thought and not given much scrutiny in code review. + ``` + var found: bool = false; + var total_found: i32 = 0; + for (var (key: i32, value: i32) in list) { + if (key == expected) { + found = true; + total_found += value; + } + } + // Include an explicit `as i64` to emphasize that we're widening the + // total at this point. + // Bug: meant to pass `total_found` not `found` here. + add_to_total(found as i64); + ``` +- An `as bool` conversion is less clear to a reader than a `!= 0` test. +- An `as bool` conversion is more verbose than a `!= 0` test. + +We could allow an `assume_as` conversion from an integer type to `bool`, where 0 +and 1 convert to `false` and `true`, and any other value is a programming error. +This would be the reverse of the `bool` -> `iN` conversion. + +Advantage: + +- Improves the completeness of our built-in conversions: any `as` conversion + can be reversed with `assume_as` (though the round-trip may be lossy). + +Disadvantage: + +- This is unlikely to be a desirable and useful operation much of the time, + and people may reach for this operation when they meant to perform a `!= 0` + test, not realizing that it means something else. + +### Bool to integer conversions + +We could disallow conversions from `bool` to `iN` types. + +Advantage: + +- More clearly demarcates the intended semantics of `bool` as a truth value + rather than as a number. +- Avoids making a choice as to whether `true` should map to 1 (zero-extension) + or -1 (sign-extension). + - But there is a strong established convention of using 1. + +Disadvantage: + +- Removes a sometimes-useful operation for which there isn't a similarly terse + alternative expression form. + - But we could add a member function `b.AsBit()` if we wanted. +- Does not expose the intended connection between the `bool` type and bits. + +We could disallow conversion from `bool` to `i1`. + +Advantage: + +- Avoids a surprising behavior where this conversion converts `true` to -1 + whereas all others convert `true` to 1. + +Disadvantage: + +- Results in non-uniform treatment of conversion from `bool`, and an awkward + special case that may get in the way of generics. +- A conversion from `bool` that produces -1 for a `true` value is useful when + producing a mask, for example in `(b as i1) as u32`. From b2b4b58e69b1c71347d7a61048f68380b38e2924 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 1 Oct 2021 16:22:19 -0700 Subject: [PATCH 09/17] Add description of precedence and associativity. --- docs/design/expressions/as_expressions.md | 53 +++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/docs/design/expressions/as_expressions.md b/docs/design/expressions/as_expressions.md index 343eba5952df..9e6e44e50fff 100644 --- a/docs/design/expressions/as_expressions.md +++ b/docs/design/expressions/as_expressions.md @@ -11,6 +11,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Table of contents - [Overview](#overview) +- [Precedence and associativity](#precedence-and-associativity) - [Built-in types](#built-in-types) - [Data types](#data-types) - [Compatible types](#compatible-types) @@ -54,6 +55,58 @@ It is possible for user-defined types to [extend](#extensibility) the set of valid explicit casts that can be performed by `as` and `assume_as`. Such extensions are expected to follow these guidelines. +## Precedence and associativity + +`as` expressions are non-associative. + +``` +var b: bool = true; +// OK +var n: i32 = (b as i1) as i32; +var m: auto = b as (bool as Hashable); +// Error, ambiguous +var m: auto = b as T as U; +``` + +The `as` operator has lower precedence than operators that visually bind +tightly: + +- prefix symbolic operators + - dereference (`*a`) + - negation (`-a`) + - complement (`~a`) +- postfix symbolic operators + - pointer type formation (`T*`), + - function call (`a(...)`), + - array indexing (`a[...]`), and + - member access (`a.m`). + +The `as` operator has higher precedence than assignment and comparison. It is +unordered with respect to binary arithmetic and bitwise operators and unary +`not`. + +``` +// OK +var x: i32* as Comparable; +// OK, `x as (U*)` not `(x as U)*`. +var y: auto = x as U*; + +var a: i32; +var b: i32; +// OK, `(a as i64) < (*x as i64)`. +if (a as i64 < *x as i64) {} +// Ambiguous: `(a + b) as i64` or `a + (b as i64)`? +var c: i32 = a + b as i64; +// Ambiguous: `(a as i64) + b` or `a as (i64 + b)`? +var d: i32 = a as i64 + b; + +// OK, `(-a) assume_as u64`, not `-(a assume_as u64)`. +var u: u64 = -a assume_as u64; + +// OK, `i32 as (GetType())`, not `(i32 as GetType)()`. +var e: i32 as GetType(); +``` + ## Built-in types ### Data types From 3c374601a8f71cfbdf8da14653e6498076cdbf08 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 1 Oct 2021 16:24:30 -0700 Subject: [PATCH 10/17] Extend precedence and associativity rules to also cover `assume_as`. --- docs/design/expressions/as_expressions.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/design/expressions/as_expressions.md b/docs/design/expressions/as_expressions.md index 9e6e44e50fff..dc4c47d7df04 100644 --- a/docs/design/expressions/as_expressions.md +++ b/docs/design/expressions/as_expressions.md @@ -57,7 +57,7 @@ extensions are expected to follow these guidelines. ## Precedence and associativity -`as` expressions are non-associative. +`as` and `assume_as` expressions are non-associative. ``` var b: bool = true; @@ -68,8 +68,8 @@ var m: auto = b as (bool as Hashable); var m: auto = b as T as U; ``` -The `as` operator has lower precedence than operators that visually bind -tightly: +The `as` and `assume_as` operators have lower precedence than operators that +visually bind tightly: - prefix symbolic operators - dereference (`*a`) @@ -81,9 +81,9 @@ tightly: - array indexing (`a[...]`), and - member access (`a.m`). -The `as` operator has higher precedence than assignment and comparison. It is -unordered with respect to binary arithmetic and bitwise operators and unary -`not`. +The `as` and `assume_as` operators have higher precedence than assignment and +comparison. They are unordered with respect to binary arithmetic, bitwise +operators, unary `not`, and each other. ``` // OK From fcc45276ae8f3ba60dda88ea0d1d25d279470932 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Thu, 7 Oct 2021 15:37:03 -0700 Subject: [PATCH 11/17] Apply suggestions from code review Co-authored-by: josh11b --- docs/design/expressions/as_expressions.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/design/expressions/as_expressions.md b/docs/design/expressions/as_expressions.md index dc4c47d7df04..1cd4e16ab461 100644 --- a/docs/design/expressions/as_expressions.md +++ b/docs/design/expressions/as_expressions.md @@ -93,7 +93,7 @@ var y: auto = x as U*; var a: i32; var b: i32; -// OK, `(a as i64) < (*x as i64)`. +// OK, `(a as i64) < ((*x) as i64)`. if (a as i64 < *x as i64) {} // Ambiguous: `(a + b) as i64` or `a + (b as i64)`? var c: i32 = a + b as i64; @@ -112,7 +112,7 @@ var e: i32 as GetType(); ### Data types In addition to the [implicit conversions](implicit_conversions.md#data-types), -the following numeric conversion is supported by `as`: +the following numeric conversions are supported by `as`: - `iN`, `uN`, or `fN` -> `fM`, for any `N` and `M`. Values that cannot be exactly represented are suitably rounded to one of the two nearest @@ -188,10 +188,10 @@ The expression `x as U` is rewritten to `x.(As(U).Convert)()`. The expression ## Alternatives considered - [Do not distinguish between safe and unsafe casts](/docs/proposals/p0845.md#merge-as-and-assume_as) -- [Do not distinguish between safe as and implicit conversions](/docs/proposals/p0845.md#as-only-performs-implicit-conversions) +- [Do not distinguish between `as` and implicit conversions](/docs/proposals/p0845.md#as-only-performs-implicit-conversions) - [Use a different name for `assume_as`](/docs/proposals/p0845.md#different-name-for-assume_as) - [Allow `iN as bool`](/docs/proposals/p0845.md#integer-to-bool-conversions) -- [Allow `bool as iN`](/docs/proposals/p0845.md#bool-to-integer-conversions) +- [Disallow `bool as iN`](/docs/proposals/p0845.md#bool-to-integer-conversions) ## References From b047a8cc68dc430cad3032ca625e6537d8ad93b4 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Thu, 7 Oct 2021 15:49:34 -0700 Subject: [PATCH 12/17] Add rationale and move argument about bool -> iN conversions to the right place. --- proposals/p0845.md | 60 +++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/proposals/p0845.md b/proposals/p0845.md index 9af889c3bd53..8c8f358edf68 100644 --- a/proposals/p0845.md +++ b/proposals/p0845.md @@ -184,15 +184,25 @@ See changes to the design for details. ## Rationale based on Carbon's goals -- [Community and culture](/docs/project/goals.md#community-and-culture) -- [Language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem) - [Performance-critical software](/docs/project/goals.md#performance-critical-software) -- [Software and language evolution](/docs/project/goals.md#software-and-language-evolution) + - Exposing an `assume_as` that assumes facts about its operand permits + Carbon developers to express their intent in a way that an optimizer can + take advantage of. - [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write) + - Providing only unsurprising built-in `as` conversions, and encouraging + user-defined types to do the same, makes code easier to understand. - [Practical safety and testing mechanisms](/docs/project/goals.md#practical-safety-and-testing-mechanisms) -- [Fast and scalable development](/docs/project/goals.md#fast-and-scalable-development) -- [Modern OS platforms, hardware architectures, and environments](/docs/project/goals.md#modern-os-platforms-hardware-architectures-and-environments) + - Syntactically distinguishing between always-safe `as` conversions and + potentially-unsafe `assume_as` conversions makes it clearer which code + should be the subject of more scrutiny when reasoning about safety. - [Interoperability with and migration from existing C++ code](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code) + - The `As` interface provides the same functionality as single-argument + `explicit` constructors and `explicit` conversion functions in C++, and + can be used to expose those operations for interoperability purposes and + as a replacement for those operations during migration. + - The `as` and `assume_as` operations together cover most of the + functionality of C++'s `static_cast`, while separating the safe casts + from the unsafe ones. ## Alternatives considered @@ -294,26 +304,8 @@ Advantage: Disadvantage: -- The treatment of all non-zero values as being "truthy" is somewhat arbitrary - and can be confusing. -- Such conversions are a known source of bugs, especially when performed - implicitly. `as` conversions will likely be fairly common and routine in - Carbon code due to their use in generics. As such, they may be written - without much thought and not given much scrutiny in code review. - ``` - var found: bool = false; - var total_found: i32 = 0; - for (var (key: i32, value: i32) in list) { - if (key == expected) { - found = true; - total_found += value; - } - } - // Include an explicit `as i64` to emphasize that we're widening the - // total at this point. - // Bug: meant to pass `total_found` not `found` here. - add_to_total(found as i64); - ``` +- The lossy treatment of all non-zero values as being "truthy" is somewhat + arbitrary and can be confusing. - An `as bool` conversion is less clear to a reader than a `!= 0` test. - An `as bool` conversion is more verbose than a `!= 0` test. @@ -343,6 +335,24 @@ Advantage: - Avoids making a choice as to whether `true` should map to 1 (zero-extension) or -1 (sign-extension). - But there is a strong established convention of using 1. +- Such conversions are a known source of bugs, especially when performed + implicitly. `as` conversions will likely be fairly common and routine in + Carbon code due to their use in generics. As such, they may be written + without much thought and not given much scrutiny in code review. + ``` + var found: bool = false; + var total_found: i32 = 0; + for (var (key: i32, value: i32) in list) { + if (key == expected) { + found = true; + total_found += value; + } + } + // Include an explicit `as i64` to emphasize that we're widening the + // total at this point. + // Bug: meant to pass `total_found` not `found` here. + add_to_total(found as i64); + ``` Disadvantage: From fee0b8b019c581356a2ff9540aaec01c77bf27d6 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 12 Oct 2021 14:30:27 -0700 Subject: [PATCH 13/17] Remove `assume_as` for now. --- docs/design/expressions/as_expressions.md | 90 ++++------- docs/design/lexical_conventions/words.md | 1 - proposals/p0845.md | 189 +++++++++------------- 3 files changed, 111 insertions(+), 169 deletions(-) diff --git a/docs/design/expressions/as_expressions.md b/docs/design/expressions/as_expressions.md index 1cd4e16ab461..c775ed9e9077 100644 --- a/docs/design/expressions/as_expressions.md +++ b/docs/design/expressions/as_expressions.md @@ -15,7 +15,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Built-in types](#built-in-types) - [Data types](#data-types) - [Compatible types](#compatible-types) - - [Pointer conversions](#pointer-conversions) - [Extensibility](#extensibility) - [Alternatives considered](#alternatives-considered) - [References](#references) @@ -41,23 +40,33 @@ way a type would be interpreted. As guidelines, an `as` conversion should be permitted when: -- The conversion is _safe_: it produces a well-defined output value for each - input value. +- The conversion is _complete_: it produces a well-defined output value for + each input value. - The conversion is _unsurprising_: the resulting value is the expected value in the destination type. -In cases where a cast is only defined for a subset of the possible inputs, an -`assume_as` expression can be used. An `assume_as` expression behaves like an -`as` expression, except that the domain of the conversion is narrower than the -entire input type, so the conversion is not safe as defined above. +For example: + +- A conversion from `fM` to `iN` is not complete, because it is not defined + for input values that are out of the range of the destination type, such as + infinities or, if `N` is too small, large finite values. +- A conversion from `iM` to `iN`, where `N` < `M`, is either not complete or + not unsurprising, because there is more than one possible expected behavior + for an input value that is not within the destination type, and those + behaviors are not substantially the same -- we could perform two's + complement wrapping, saturate, or produce undefined behavior analogous to + arithmetic overflow. +- A conversion from `iM` to `fN` can be unsurprising, because even though + there may be a choice of which way to round, the possible values are + substantially the same. It is possible for user-defined types to [extend](#extensibility) the set of -valid explicit casts that can be performed by `as` and `assume_as`. Such -extensions are expected to follow these guidelines. +valid explicit casts that can be performed by `as`. Such extensions are expected +to follow these guidelines. ## Precedence and associativity -`as` and `assume_as` expressions are non-associative. +`as` expressions are non-associative. ``` var b: bool = true; @@ -68,8 +77,8 @@ var m: auto = b as (bool as Hashable); var m: auto = b as T as U; ``` -The `as` and `assume_as` operators have lower precedence than operators that -visually bind tightly: +The `as` operator has lower precedence than operators that visually bind +tightly: - prefix symbolic operators - dereference (`*a`) @@ -81,9 +90,8 @@ visually bind tightly: - array indexing (`a[...]`), and - member access (`a.m`). -The `as` and `assume_as` operators have higher precedence than assignment and -comparison. They are unordered with respect to binary arithmetic, bitwise -operators, unary `not`, and each other. +The `as` operator has higher precedence than assignment and comparison. It is +unordered with respect to binary arithmetic, bitwise operators, and unary `not`. ``` // OK @@ -100,8 +108,10 @@ var c: i32 = a + b as i64; // Ambiguous: `(a as i64) + b` or `a as (i64 + b)`? var d: i32 = a as i64 + b; -// OK, `(-a) assume_as u64`, not `-(a assume_as u64)`. -var u: u64 = -a assume_as u64; +// OK, `(-a) as f64`, not `-(a as f64)`. +// Unfortunately, the former is undefined if `a` is `i32.MinValue()`; +// the latter is not. +var u: f64 = -a as f64; // OK, `i32 as (GetType())`, not `(i32 as GetType)()`. var e: i32 as GetType(); @@ -122,24 +132,13 @@ the following numeric conversions are supported by `as`: - `bool` -> `iN` or `uN`. `false` converts to `0` and `true` converts to `1` (or to `-1` for `i1`). -The following additional numeric conversions are supported by `assume_as`: - -- `iN` or `uN` -> `iM` or `uM`, for any `N` and `M`. It is a programming error - if the source value cannot be represented in the destination type. - - **TODO:** Once we have a two's complement truncation operation with defined - behavior on overflow, link to it from here as an alternative. - -- `fN` -> `iM`, for any `N` and `M`. Values that cannot be exactly represented - are suitably rounded to one of the two nearest representable values. It is a - programming error if the source value does not round to an integer that can - be represented in the destination type. - Conversions from numeric types to `bool` are not supported with `as`; instead of using `as bool`, such conversions can be performed with `!= 0`. -**Note:** The precise rounding rules for these conversions have not yet been -decided. +Lossy conversions between `iN` or `uN` and `iM` or `uM` are not supported with +`as`, and similarly conversions from `fN` to `iM` are not supported. + +**Future work:** Add mechanisms to perform these conversions. ### Compatible types @@ -152,44 +151,23 @@ The following conversion is supported by `as`: adapters are permitted and which code can perform them. Some of the conversions permitted by this rule may only be allowed in certain contexts. -### Pointer conversions - -The following pointer conversion is supported by `assume_as`: - -- `T*` -> `U*` if `U` is a subtype of `T`. - -This cast converts in the opposite direction to the corresponding -[implicit conversion](implicit_conversions.md#pointer-conversions). It is a -programming error if the source pointer does not point to a `U` object. - -**Note:** `assume_as` cannot convert between unrelated pointer types, because -there are no input values for which the conversion would produce a well-defined -output value. Separate facilities will be provided for reinterpreting memory as -a distinct type. - ## Extensibility Explicit casts can be defined for user-defined types such as -[classes](../classes.md) by implementing the `As` or `AssumeAs` interface: +[classes](../classes.md) by implementing the `As` interface: ``` -interface AssumeAs(Dest:! Type) { +interface As(Dest:! Type) { fn Convert[me: Self]() -> Dest; } -interface As(Dest:! Type) extends AssumeAs(Dest) { - // Inherited from AssumeAs(Dest): - // fn Convert[me: Self]() -> Dest; -} ``` -The expression `x as U` is rewritten to `x.(As(U).Convert)()`. The expression -`x assume_as U` is rewritten to `x.(AssumeAs(U).Convert)()`. +The expression `x as U` is rewritten to `x.(As(U).Convert)()`. ## Alternatives considered - [Do not distinguish between safe and unsafe casts](/docs/proposals/p0845.md#merge-as-and-assume_as) - [Do not distinguish between `as` and implicit conversions](/docs/proposals/p0845.md#as-only-performs-implicit-conversions) -- [Use a different name for `assume_as`](/docs/proposals/p0845.md#different-name-for-assume_as) - [Allow `iN as bool`](/docs/proposals/p0845.md#integer-to-bool-conversions) - [Disallow `bool as iN`](/docs/proposals/p0845.md#bool-to-integer-conversions) diff --git a/docs/design/lexical_conventions/words.md b/docs/design/lexical_conventions/words.md index c97f31d8a91f..98e754602293 100644 --- a/docs/design/lexical_conventions/words.md +++ b/docs/design/lexical_conventions/words.md @@ -38,7 +38,6 @@ The following words are interpreted as keywords: - `and` - `api` - `as` -- `assume_as` - `auto` - `base` - `break` diff --git a/proposals/p0845.md b/proposals/p0845.md index 8c8f358edf68..f0ca3b039080 100644 --- a/proposals/p0845.md +++ b/proposals/p0845.md @@ -17,9 +17,10 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Proposal](#proposal) - [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals) - [Alternatives considered](#alternatives-considered) - - [Merge `as` and `assume_as`](#merge-as-and-assume_as) + - [Provide an additional operator for unsafe conversions](#provide-an-additional-operator-for-unsafe-conversions) + - [Allow `as` to perform some unsafe conversions](#allow-as-to-perform-some-unsafe-conversions) + - [Allow `as` to perform two's complement truncation](#allow-as-to-perform-twos-complement-truncation) - [`as` only performs implicit conversions](#as-only-performs-implicit-conversions) - - [Different name for `assume_as`](#different-name-for-assume_as) - [Integer to bool conversions](#integer-to-bool-conversions) - [Bool to integer conversions](#bool-to-integer-conversions) @@ -37,24 +38,6 @@ We would like to provide a notation for the following operations: return a / (b as f64); } ``` -- Requesting a type conversion to a narrower type, where the developer - believes the value is a value of the narrower type: - ``` - fn Extract(p: Base*) -> i32 { - if (p->kind == DerivedKind) { - // I promise that p points to a Derived. - return (p assume_as Derived*)->value; - } - return -1; - } - fn ByteEncode(n: i32) -> Optional(i8) { - if (n >= i8.MinValue and n <= i8.MaxValue) { - // I promise that n fits in i8. - return .Some(n assume_as i8); - } - return .None; - } - ``` - Specifying the type that an expression will have or will be converted into, for documentation purposes. ``` @@ -79,8 +62,7 @@ We would like to provide a notation for the following operations: In general, the developer wants to specify that an expression should be considered to produce a value of a particular type, and that type might be more general than the type of the expression, the same as the type of the expression, -more specific than the type of the expression, or perhaps might represent a -different way of viewing the value. +or perhaps might represent a different way of viewing the value. The first of the above problems is especially important in Carbon due to the use of facet types for generics. Explicit conversions of types to interfaces will be @@ -91,7 +73,8 @@ different meanings. For this proposal, the following are out of scope: - Requesting a type conversion that changes the value, such as by truncation. -- Determining whether a value can be represented in a narrower type. +- Converting a value to a narrower type or determining whether such a + conversion is possible -- `try_as` or `as?` operations. ## Background @@ -160,57 +143,70 @@ the expression `x` that is consistent with `T`, and is used: ## Proposal -Carbon provides two explicit cast operations: - -- `x as T` performs an unsurprising and safe conversion from `x` to type `T`. - - This can be used to perform any implicit conversion explicitly. As in - Swift, this can therefore be used to convert from subtype to supertype. - - This can also be used to perform an unsurprising and safe conversion - that cannot be performed implicitly because it's lossy, such as from - `i32` to `f32`. -- `x assume_as T` performs an unsurprising conversion from `x` to `T`, in the - case where it is not necessarily statically known that `x` can be - represented in type `T`. The value of `x` is assumed to be representable in - type `T`, and the case where `x` cannot be represented in type `T` is a - programming error. - - As in Swift, this can be used to convert from supertype to subtype. - - This can also be used to perform a conversion that is unsurprising and - safe for some subset of the possible values of type `x`, such as from - `f32` to `i32`. - - Any conversion that can be performed by `as` can also be performed by - `assume_as`. +Carbon provides a binary `as` operator. + +`x as T` performs an unsurprising and safe conversion from `x` to type `T`. + +- This can be used to perform any implicit conversion explicitly. As in Swift, + this can therefore be used to convert from subtype to supertype. +- This can also be used to perform an unsurprising and safe conversion that + cannot be performed implicitly because it's lossy, such as from `i32` to + `f32`. + +This operator does not perform conversions with domain restrictions, such as +converting from `f32` to `i64`, where sufficiently large values can't be +converted. It does not perform operations in which there are multiple different +reasonable interpretations, such as converting from `i64` to `i32`, where a +two's complement truncation might sometimes be reasonable but where the intent +is more likely that it is an error to convert a value that does not fit into an +`i32`. See changes to the design for details. ## Rationale based on Carbon's goals -- [Performance-critical software](/docs/project/goals.md#performance-critical-software) - - Exposing an `assume_as` that assumes facts about its operand permits - Carbon developers to express their intent in a way that an optimizer can - take advantage of. - [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write) - Providing only unsurprising built-in `as` conversions, and encouraging user-defined types to do the same, makes code easier to understand. - [Practical safety and testing mechanisms](/docs/project/goals.md#practical-safety-and-testing-mechanisms) - Syntactically distinguishing between always-safe `as` conversions and - potentially-unsafe `assume_as` conversions makes it clearer which code - should be the subject of more scrutiny when reasoning about safety. + potentially-unsafe conversions being performed by other syntax makes it + clearer which code should be the subject of more scrutiny when reasoning + about safety. - [Interoperability with and migration from existing C++ code](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code) - The `As` interface provides the same functionality as single-argument `explicit` constructors and `explicit` conversion functions in C++, and can be used to expose those operations for interoperability purposes and as a replacement for those operations during migration. - - The `as` and `assume_as` operations together cover most of the - functionality of C++'s `static_cast`, while separating the safe casts - from the unsafe ones. ## Alternatives considered -### Merge `as` and `assume_as` +### Provide an additional operator for unsafe conversions + +We could provide an additional casting operator, such as `assume_as` or +`unsafe_as`, to model conversions that have a domain restriction, such as +`i64 -> i32` or `f32 -> i64` or `Base*` -> `Derived*`. + +Advantage: + +- Provides additional important but unsafe functionality. +- Gives this functionality the appearance of being a central language feature. +- Separates safe conversions from unsafe ones. + +Disadvantage: + +- Increases complexity. +- The connection between these conversions may not be obvious, and the kind + and amount of unsafety in practice differs substantially between them. + +This alternative is not rejected by this proposal, but its consideration has +been deferred for now. + +### Allow `as` to perform some unsafe conversions -We could provide a single type-casting operator that can perform more of the -conversions supported by `assume_as`. The remaining conversions could be -relegated to some other syntax. +We could provide a single type-casting operator that can perform some +conversions that have a domain restriction, treating values out of range as +programming errors. One particularly appealing option would be to permit `as` to convert freely between integer and floating-point types, but not permit it to convert from @@ -233,61 +229,45 @@ Disadvantage: - Under maintenance, every usage of `as` would need additional scrutiny because it's not in general a safe operation. -### `as` only performs implicit conversions +### Allow `as` to perform two's complement truncation -We could limit `as` to performing only implicit conversions. This would mean -that `as` cannot perform lossy conversions. +We could allow `as` to convert between any two integer types, performing a two's +complement conversion between these types. Advantage: -- One fewer set of rules for developers to be aware of. +- Familiar to developers from C++ and various other systems programming + languages. Disadvantage: -- Converting between integer and floating-point types is common, and providing - built-in syntax for it seems valuable. +- Hides the difference between safe and predictable conversions and + conversions that can produce surprising values on overflow. +- Introducing a common and easy notation for conversion with wraparound means + that this notation will also be used in the -- likely much more common -- + case of wanting to truncate a value that is already known to be in-bounds. + Compared to having distinct notation for these two operations: + - This removes the ability to distinguish between programming errors due + to overflow and intentional wraparound by using the same syntax for + both, both for readers of the code and for automated checks in debugging + builds. + - This removes the ability to optimize on the basis of knowing that a + value is expected to be in-bounds when performing a narrowing + conversion. -### Different name for `assume_as` - -We could use a different name for `assume_as`, such as `unsafe_as` or `as!`. - -Advantage of `as!`: - -- Terser. - -Disadvantage of `as!`: - -- Including a `!` as a suffix on a word-shaped operator would be novel and - risks making the lexing rules less uniform. -- We are planning for the `!` punctuation symbol to be used to indicate a - compile-time operation, such as a generic or template parameter, or a - metaprogramming operation. - -Advantage of `unsafe_as`: - -- Clearer that violation of the assumption is a programming error. - -Disadvantage of `unsafe_as`: +### `as` only performs implicit conversions -- Less clear about what aspect of the conversion is unsafe, and that the - conversion is safe if the input value is suitable. +We could limit `as` to performing only implicit conversions. This would mean +that `as` cannot perform lossy conversions. -Advantage of `assert_as`: +Advantage: -- Term "assert" may be more familiar than "assume". +- One fewer set of rules for developers to be aware of. -Disadvantage of `assert_as`: +Disadvantage: -- The emphasis of "assert" is on checking, and it's not clear whether we will - implement checks for all the kinds of programming error that can result from - `assume_as`, in particular for pointer downcasts. Even if such checks are - implemented, they may not be enabled in a normal debugging build. -- An assertion failure is usually defined to either produce a trap with a - diagnostic message or to allow the program to continue running and - potentially handle the situation in a different way. An assumption failure - is usually defined in a way that permits anything to happen, so that we can - optimize on the basis that the situation is not reached. The latter is the - intended semantic model for `assume_as`. +- Converting between integer and floating-point types is common, and providing + built-in syntax for it seems valuable. ### Integer to bool conversions @@ -309,21 +289,6 @@ Disadvantage: - An `as bool` conversion is less clear to a reader than a `!= 0` test. - An `as bool` conversion is more verbose than a `!= 0` test. -We could allow an `assume_as` conversion from an integer type to `bool`, where 0 -and 1 convert to `false` and `true`, and any other value is a programming error. -This would be the reverse of the `bool` -> `iN` conversion. - -Advantage: - -- Improves the completeness of our built-in conversions: any `as` conversion - can be reversed with `assume_as` (though the round-trip may be lossy). - -Disadvantage: - -- This is unlikely to be a desirable and useful operation much of the time, - and people may reach for this operation when they meant to perform a `!= 0` - test, not realizing that it means something else. - ### Bool to integer conversions We could disallow conversions from `bool` to `iN` types. From e014b0827a75263bbee1bca2931c3f5b2d477311 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 12 Oct 2021 17:09:39 -0700 Subject: [PATCH 14/17] Move `assume_as` from alternatives considered to future work. This isn't an alternative; it's complementary to the current proposal. --- proposals/p0845.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/proposals/p0845.md b/proposals/p0845.md index f0ca3b039080..00ac7d56f79c 100644 --- a/proposals/p0845.md +++ b/proposals/p0845.md @@ -16,8 +16,9 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Background](#background) - [Proposal](#proposal) - [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals) -- [Alternatives considered](#alternatives-considered) +- [Future work](#future-work) - [Provide an additional operator for unsafe conversions](#provide-an-additional-operator-for-unsafe-conversions) +- [Alternatives considered](#alternatives-considered) - [Allow `as` to perform some unsafe conversions](#allow-as-to-perform-some-unsafe-conversions) - [Allow `as` to perform two's complement truncation](#allow-as-to-perform-twos-complement-truncation) - [`as` only performs implicit conversions](#as-only-performs-implicit-conversions) @@ -179,7 +180,7 @@ See changes to the design for details. can be used to expose those operations for interoperability purposes and as a replacement for those operations during migration. -## Alternatives considered +## Future work ### Provide an additional operator for unsafe conversions @@ -199,8 +200,10 @@ Disadvantage: - The connection between these conversions may not be obvious, and the kind and amount of unsafety in practice differs substantially between them. -This alternative is not rejected by this proposal, but its consideration has -been deferred for now. +If we don't follow this direction, we will need to provide these operations by +another mechanism, such as named function calls, for example: `n.Truncate(i32)`. + +## Alternatives considered ### Allow `as` to perform some unsafe conversions From da570b035949e2d8175b401c1479ee2fc7cd9e37 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 19 Oct 2021 17:47:53 -0700 Subject: [PATCH 15/17] Expand advantages / disadvantages in response to review comments. --- proposals/p0845.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/proposals/p0845.md b/proposals/p0845.md index 00ac7d56f79c..2dabfa7e61ca 100644 --- a/proposals/p0845.md +++ b/proposals/p0845.md @@ -222,15 +222,23 @@ Advantage: - This would make `as` more consistent with arithmetic operations, which will likely have no overt indication that they're unsafe in the presence of integer overflow. +- If we don't do this, then code mixing differently-sized types will need to + use a syntactic notation other than `as`, even if all conversions remain + in-bounds. If such code is common, as it is in C++ (for example, when mixing + `int` and `size_t`), developers may become accustomed to using that "assume + in range" notation and not consider it to be a warning sign, thereby eroding + the advantage of using a distinct notation. Disadvantage: -- No clear foundation for which conversions can be performed by `as` and which - cannot. +- If we allow this conversion, there would be no clear foundation for which + conversions can be performed by `as` and which cannot in general. - An `as` expression would be less suitable for selecting which operation to perform if it can be unsafe. - Under maintenance, every usage of `as` would need additional scrutiny because it's not in general a safe operation. +- This risks being surprising to developers coming from C and C++ where + integer type conversions are always safe. ### Allow `as` to perform two's complement truncation @@ -246,6 +254,9 @@ Disadvantage: - Hides the difference between safe and predictable conversions and conversions that can produce surprising values on overflow. +- Makes `as` conversions have behavior that diverges from the behavior of + arithmetic, where we expect at least signed overflow to be considered a + programming error rather than being guaranteed to wrap around. - Introducing a common and easy notation for conversion with wraparound means that this notation will also be used in the -- likely much more common -- case of wanting to truncate a value that is already known to be in-bounds. From 8314f5f6082f93bb054bef977fbc2a454da620f6 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Thu, 21 Oct 2021 10:04:38 -0700 Subject: [PATCH 16/17] Weaken stance on lossy as-conversions between integer types. --- proposals/p0845.md | 54 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/proposals/p0845.md b/proposals/p0845.md index 2dabfa7e61ca..f4a2b7e894e7 100644 --- a/proposals/p0845.md +++ b/proposals/p0845.md @@ -17,7 +17,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Proposal](#proposal) - [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals) - [Future work](#future-work) - - [Provide an additional operator for unsafe conversions](#provide-an-additional-operator-for-unsafe-conversions) + - [Provide a mechanism for unsafe conversions](#provide-a-mechanism-for-unsafe-conversions) + - [Casting operator for conversions with domain restrictions](#casting-operator-for-conversions-with-domain-restrictions) - [Alternatives considered](#alternatives-considered) - [Allow `as` to perform some unsafe conversions](#allow-as-to-perform-some-unsafe-conversions) - [Allow `as` to perform two's complement truncation](#allow-as-to-perform-twos-complement-truncation) @@ -182,7 +183,46 @@ See changes to the design for details. ## Future work -### Provide an additional operator for unsafe conversions +### Provide a mechanism for unsafe conversions + +We need to provide additional conversions beyond those proposed for `as`. In +particular, to supply the same set of conversions as C++, we would need at least +the following conversions that don't match the rules for `as`: + +Conversions with a domain restriction: + +- Conversions from pointer-to-supertype to pointer-to-subtype. +- Conversions from floating-point to integer types that assume the input is + in-range. +- (Not in C++.) Conversions between any two integer types that assume the + input is in-range. + +Conversions that modify some values: + +- Truncating conversions between any two integer types. + +Conversions that reinterpret values: + +- Conversions between arbitrary pointer types. +- Conversions between integers and pointers. +- Bit-casts between arbitrary, sufficiently-trivial types of the same size. + +Special cases: + +- Some analogue of `dynamic_cast`. +- Some analogue of `const_cast`. + +We will need to decide which of these we wish to provide -- in particular, +depending on our plans for mutability and RTTI, `const_cast` and `dynamic_cast` +may or may not be appropriate. + +For the operations we do supply, we could provide either named functions or +dedicated language syntax. While this proposal suggests that the `as` operator +should not be the appropriate language syntax for the above cases, that decision +should be revisited once we have more information from examining the +alternatives. + +#### Casting operator for conversions with domain restrictions We could provide an additional casting operator, such as `assume_as` or `unsafe_as`, to model conversions that have a domain restriction, such as @@ -201,7 +241,7 @@ Disadvantage: and amount of unsafety in practice differs substantially between them. If we don't follow this direction, we will need to provide these operations by -another mechanism, such as named function calls, for example: `n.Truncate(i32)`. +another mechanism, such as named function calls. ## Alternatives considered @@ -240,6 +280,10 @@ Disadvantage: - This risks being surprising to developers coming from C and C++ where integer type conversions are always safe. +The choice to not provide these operations with `as` is experimental, and should +be revisited when we have more information about the design of integer types and +their behavior. + ### Allow `as` to perform two's complement truncation We could allow `as` to convert between any two integer types, performing a two's @@ -269,6 +313,10 @@ Disadvantage: value is expected to be in-bounds when performing a narrowing conversion. +The choice to not provide these operations with `as` is experimental, and should +be revisited when we have more information about the design of integer types and +their behavior. + ### `as` only performs implicit conversions We could limit `as` to performing only implicit conversions. This would mean From b9e821c852f9af0cfa00bb85cb1ab6b808b27f01 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 27 Oct 2021 16:27:50 -0700 Subject: [PATCH 17/17] Remove bullet point that is subsumed by a more general bullet point. --- proposals/p0845.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/proposals/p0845.md b/proposals/p0845.md index f4a2b7e894e7..dce8cdbab9dc 100644 --- a/proposals/p0845.md +++ b/proposals/p0845.md @@ -296,8 +296,6 @@ Advantage: Disadvantage: -- Hides the difference between safe and predictable conversions and - conversions that can produce surprising values on overflow. - Makes `as` conversions have behavior that diverges from the behavior of arithmetic, where we expect at least signed overflow to be considered a programming error rather than being guaranteed to wrap around.