# Indexing
# Table of contents
# Overview
Carbon supports indexing using the conventional a[i] subscript syntax. When
a is a
durable reference expression,
the result of subscripting is also a durable reference expression, but when a
is a value expression, the result
can be a durable reference expression or a value expression, depending on which
interface the type implements:
- If subscripting a value expression produces a value expression, as with an
array, the type should implement
IndexWith. - If subscripting a value expression produces a durable reference expression,
as with C++'s
std::span, the type should implementIndirectIndexWith.
IndirectIndexWith is a subtype of IndexWith, and subscript expressions are
rewritten to method calls on IndirectIndexWith if the type is known to
implement that interface, or to method calls on IndexWith otherwise.
IndirectIndexWith provides a final blanket impl of IndexWith, so a type
can implement at most one of those two interfaces.
The Ref methods of these interfaces, which are used to form durable reference
expressions on indexing, must return by ref.
# Details
A subscript expression has the form "lhs [ index ]". As in C++, this
syntax has the same precedence as ., ->, and function calls, and associates
left-to-right with all of them.
Its semantics are defined in terms of the following interfaces:
interface IndexWith(SubscriptType:! type) {
let ElementType:! type;
fn At[bound self: Self](subscript: SubscriptType) -> val ElementType;
fn Ref[bound ref self: Self](subscript: SubscriptType) -> ref ElementType;
}
interface IndirectIndexWith(SubscriptType:! type) {
require Self impls IndexWith(SubscriptType);
fn Ref[bound self: Self](subscript: SubscriptType) -> ref ElementType;
}
A subscript expression where lhs has type T and index has type I is
rewritten based on the expression category of lhs and whether T is known to
implement IndirectIndexWith(I):
- If
TimplementsIndirectIndexWith(I), the expression is rewritten to "(lhs).(IndirectIndexWith(I).Ref)(index)". - Otherwise, if lhs is a
_durable reference expression_,
the expression is rewritten to "
(lhs).(IndexWith(I).Ref)(index)". - Otherwise, the expression is rewritten to "
(lhs).(IndexWith(I).At)(index)".
IndirectIndexWith provides a blanket final impl for IndexWith:
final impl forall
[SubscriptType:! type, T:! IndirectIndexWith(SubscriptType)]
T as IndexWith(SubscriptType) {
where ElementType = T.(IndirectIndexWith(SubscriptType).ElementType);
fn At[bound self: Self](subscript: SubscriptType) -> val ElementType {
return self.(IndirectIndexWith(SubscriptType).Ref)(index);
}
fn Ref[bound ref self: Self](subscript: SubscriptType) -> ref ElementType {
return self.(IndirectIndexWith(SubscriptType).Ref)(index);
}
}
Thus, a type that implements IndirectIndexWith need not, and cannot, provide
its own definitions of IndexWith.At and IndexWith.Ref.
# Examples
An array type could implement subscripting like so:
class Array(template T:! type) {
impl as IndexWith(like i64) {
let ElementType:! type = T;
fn At[bound self: Self](subscript: i64) -> val T;
fn Ref[bound ref self: Self](subscript: i64) -> ref T;
}
}
And a type such as std::span could look like this:
class Span(T:! type) {
impl as IndirectIndexWith(like i64) {
let ElementType:! type = T;
fn Ref[bound ref self: Self](subscript: i64) -> ref T;
}
}
# Alternatives considered
- Different subscripting syntaxes
- Multiple indices
- Read-only subscripting
- Rvalue-only subscripting
- Map-like subscripting
# References
- Proposal #2274: Subscript syntax and semantics
- Proposal #2006: Values, variables, and pointers