Files
carbon-lang/docs/design/expressions/implicit_conversions.md
T

300 lines
12 KiB
Markdown

# Implicit conversions
<!--
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->
<!-- toc -->
## Table of contents
- [Overview](#overview)
- [Properties of implicit conversions](#properties-of-implicit-conversions)
- [Lossless](#lossless)
- [Semantics-preserving](#semantics-preserving)
- [Examples](#examples)
- [Built-in types](#built-in-types)
- [Data types](#data-types)
- [Equivalent types](#equivalent-types)
- [Pointer conversions](#pointer-conversions)
- [Pointer conversion examples](#pointer-conversion-examples)
- [Type-of-types](#type-of-types)
- [Semantics](#semantics)
- [Extensibility](#extensibility)
- [Alternatives considered](#alternatives-considered)
- [References](#references)
<!-- tocstop -->
## Overview
When an expression appears in a context in which an expression of a specific
type is expected, the expression is implicitly converted to that type if
possible.
For [built-in types](#built-in-types), implicit conversions are permitted when:
- The conversion is [_lossless_](#lossless): every possible value for the
source expression converts to a distinct value in the target type.
- The conversion is [_semantics-preserving_](#semantics-preserving):
corresponding values in the source and destination type have the same
abstract meaning.
These rules aim to ensure that implicit conversions are unsurprising: the value
that is provided as the operand of an operation should match how that operation
interprets the value, because the identity and abstract meaning of the value are
preserved by any implicit conversions that are applied.
It is possible for user-defined types to [extend](#extensibility) the set of
valid implicit conversions. Such extensions are expected to also follow these
rules.
## Properties of implicit conversions
### Lossless
We expect implicit conversion to never lose information: if two values are
distinguishable before the conversion, they should generally be distinguishable
after the conversion. It should be possible to define a conversion in the
opposite direction that restores the original value, but such a conversion is
not expected to be provided in general, and might be computationally expensive.
Because an implicit conversion is converting from a narrower type to a wider
type, implicit conversions do not necessarily preserve static information about
the source value.
### Semantics-preserving
We expect implicit conversions to preserve the meaning of converted values. The
assessment of this criterion will necessarily be subjective, because the
meanings of values generally live in the mind of the programmer rather than in
the program text. However, the semantic interpretation is expected to be
consistent from one conversion to another, so we can provide a test: if multiple
paths of implicit conversions from a type `A` to a type `B` exist, and the same
value of type `A` would convert to different values of type `B` along different
paths, then at least one of those conversions must not be semantics-preserving.
A semantics-preserving conversion does not necessarily preserve the meaning of
particular syntax when applied to the value. The same syntax may map to
different operations in the new type. For example, division may mean different
things in integer and floating-point types, and member access may find different
members in a derived class pointer versus in a base class pointer.
### Examples
Conversion from `i32` to `Vector(int)` by forming a vector of N zeroes is
lossless but not semantics-preserving.
Conversion from `i32` to `f32` by rounding to the nearest representable value is
semantics-preserving but not lossless.
Conversion from `String` to `StringView` is lossless, because we can compute the
`String` value from the `StringView` value, and semantics-preserving because the
string value denoted is the same. Conversion in the other direction may or may
not be semantics-preserving depending on whether we consider the address to be a
salient part of a `StringView`'s value.
## Built-in types
### Data types
The following implicit numeric conversions are available:
- `iN` or `uN` -> `iM` if `M` > `N`
- `uN` -> `uM` if `M` > `N`
- `fN` -> `fM` if `M` > `N`
- `iN` or `uN` -> `fM` if every value of type `iN` or `uN` can be represeted
in `fM`:
- `i12` or `u11` (or smaller) -> `f16`
- `i25` or `u24` (or smaller) -> `f32`
- `i54` or `u53` (or smaller) -> `f64`
- `i65` or `u64` (or smaller) -> `f80` (x86 only)
- `i114` or `u113` (or smaller) -> `f128` (if available)
- `i238` or `u237` (or smaller) -> `f256` (if available)
In each case, the numerical value is the same before and after the conversion.
An integer zero is translated into a floating-point positive zero.
An integer constant can be implicitly converted to any type `iM`, `uM`, or `fM`
in which that value can be exactly represented. A floating-point constant can be
implicitly converted to any type `fM` in which that value is between the least
representable finite value and the greatest representable finite value
(inclusive), and converts to the nearest representable finite value, with ties
broken by picking the value for which the mantissa is even.
The above conversions are also precisely those that C++ considers non-narrowing,
except:
- Carbon also permits integer to floating-point conversions in more cases. The
most important of these is that Carbon permits `i32` to be implicitly
converted to `f64`. Lossy conversions, such as from `i32` to `f32`, are not
permitted.
- What Carbon considers to be an integer constant or floating-point constant
may differ from what C++ considers to be a constant expression.
**Note:** We have not yet decided what will qualify as a constant in this
context, but it will include at least integer and floating-point literals,
with optional enclosing parentheses. It is possible that such constants will
have singleton types; see issue
[#508](https://github.com/carbon-language/carbon-lang/issues/508).
### Equivalent types
The following conversion is available:
- `T` -> `U` if `T` is equivalent to `U`
Two types are equivalent only if they have the same set of values with the same
meaning and the same representation, with the same set of capabilities and
constraints, where the only difference is how the type interprets operations on
values of that type.
`T` is equivalent to `U` if:
- `T` is the same type as `U`, or
- `T` is the facet type `U as SomeInterface`, or
- `U` is the facet type `T as SomeInterface`, or
- `T` is `A*`, `U` is `B*`, and `A` is equivalent to `B`, or
- for some type `V`, `T` is equivalent to `V` and `V` is equivalent to `U`.
**Note:** More type equivalence rules are expected to be added over time.
A prerequisite for types being equivalent is that they are
[compatible](../generics/terminology.md#compatible-types), and in particular
that they have the same set of values and the same representation for those
values. However, types being compatible does not imply that an implicit
conversion, or even an explicit cast, between those types is necessarily valid.
This is because the type of a value models not only the representation of the
value but also the capabilities that a user of the value has to interact with
the value. Two compatible types may expose different capabilities, such as the
capability to mutate the object or to access its implementation details, and
conversions between such types may require an explicit cast if the conversion is
possible at all.
### Pointer conversions
The following pointer conversion is available:
- `T*` -> `U*` if `T` is a subtype of `U`.
`T` is a subtype of `U` if:
- `T` is equivalent to `U`, as described above, or
- `T` is equivalent to a class derived from a class equivalent to `U`.
**Note:** More type subtyping rules are expected to be added over time.
`T*` is not necessarily a subtype of `U*` even if `T` is a subtype of `U`. For
example, we can convert `Derived*` to `Base*`, but cannot convert `Derived**` to
`Base**` because that would allow storing a `Derived2*` into a `Derived*`:
```
abstract class Base {}
class Derived extends Base {}
class Derived2 extends Base {}
var d2: Derived2 = {};
var p: Derived*;
var q: Derived2* = &d2;
var r: Base** = &p;
// Bad: would store q to p.
*r = q;
```
**Note:** If we add `const` qualification, we could treat `const T*` as a
subtype of `const U*` if `T` is a subtype of `U`, and could treat `T` as a
subtype of `const T`.
#### Pointer conversion examples
With these classes:
```
base class C;
let F: auto = C as Hashable;
class D extends C;
```
These implicit pointer conversions are permitted:
- `D*` -> `C*`: `D` is a subtype of `C`
- `F*` -> `C*`: `F` is equivalent to `C`, so `F` is a subtype of `C`
- `C*` -> `F*`: `C` is equivalent to `F`, so `C` is a subtype of `F`
- `F**` -> `C**`: `F` is equivalent to `C`, so `F*` is equivalent to `C*`, so
`F*` is a subtype of `C*`
- `D*` -> `F*`: `D` is derived from `C` and `C` is equivalent to `D`, so `D`
is a subtype of `F`
These implicit pointer conversions are disallowed:
- `C*` -> `D*`: `C` is not a subtype of `D`
- `D**` -> `C**`: `D*` is not a subtype of `C*`
Note that "equivalent to" means we can freely convert back and forwards; the
difference in the types is just changing which operations are surfaced, not
changing anything about the interpretation or switching between different
abstractions. In contrast, "subtype of" permits conversion from a more specific
type to a more general type, so the reverse conversion is not necessarily valid.
### Type-of-types
A type `T` with [type-of-type](../generics/terminology.md#type-of-type) `TT1`
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
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.
## Extensibility
Implicit conversions can be defined for user-defined types such as
[classes](../classes.md) by implementing the `ImplicitAs` interface:
```
interface As(Dest:! Type) {
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
transitivity would introduce the risk of ambiguity issues as code evolves and
would in general require a search of a potentially unbounded set of intermediate
types.
## Alternatives considered
- [Provide lossy and non-semantics-preserving implicit conversions from C++](/docs/proposals/p0820.md#c-conversions)
- [Provide no implicit conversions](/docs/proposals/p0820.md#no-conversions)
- [Provide no extensibility](/docs/proposals/p0820.md#no-extensibility)
- [Apply implicit conversions transitively](/docs/proposals/p0820.md#transitivity)
## References
- [Implicit conversions in C++](https://en.cppreference.com/w/cpp/language/implicit_conversion)
- Proposal
[#820: Implicit conversions](https://github.com/carbon-language/carbon-lang/pull/820).
- Proposal
[#866: Allow ties in floating literals](https://github.com/carbon-language/carbon-lang/pull/866).