Carbon provides a conventional set of operators for operating on bits:
var a: u8 = 5;
var b: u8 = 3;
var c: i8 = -5;
// 250
var complement: u8 = ^a;
// 1
var bitwise_and: u8 = a & b;
// 7
var bitwise_or: u8 = a | b;
// 6
var bitwise_xor: u8 = a ^ b;
// 40
var left_shift: u8 = a << b;
// 2
var logical_right_shift: u8 = a >> 1;
// -3
var arithmetic_right_shift: i8 = c >> 1;
These operators have predefined meanings for Carbon's integer types. User-defined types can define the meaning of these operations by implementing an interface provided as part of the Carbon standard library.
%%{init: {'themeVariables': {'fontFamily': 'monospace'}}}%%
graph BT
complement["^x"]
bitwise_and>"x & y"]
bitwise_or>"x | y"]
bitwise_xor>"x ^ y"]
shift["x << y<br>
x >> y"]
bitwise_and & bitwise_or & bitwise_xor & shift --> complement
Instructions for reading this diagram.
Parentheses are required when mixing different bitwise and bit-shift operators.
Binary &, |, and ^ are left-associative. The bit-shift operators << and
>> are non-associative.
// ✅ Same as (1 | 2) | 4, evaluates to 7.
var a: i32 = 1 | 2 | 4;
// ❌ Error, parentheses are required to distinguish between
// (3 | 5) & 6, which evaluates to 6, and
// 3 | (5 & 6), which evaluates to 7.
var b: i32 = 3 | 5 & 6;
// ❌ Error, parentheses are required to distinguish between
// (1 << 2) << 3, which evaluates to 4 << 3 == 32, and
// 1 << (2 << 3), which evaluates to 1 << 16 == 65536.
var c: i32 = 1 << 2 << 3;
// ❌ Error, can't repeat the `^` operator. Use `^(^4)` or simply `4`.
var d: i32 = ^^4;
Bitwise and bit-shift operators are supported for Carbon's built-in integer types, and, unless that behavior is overridden, for types that can be implicitly converted to integer types, as follows:
For binary bitwise operators, if one operand has an integer type and the other operand can be implicitly converted to that type, then it is. If both operands are of integer type, this results in the following conversions:
uN and uM, or they are iN and iM, the operands are
converted to the larger type.iN and the other type is uM, and M < N, the uM
operand is converted to iN.A built-in binary bitwise &, |, or ^ operation is performed if, after the
above conversion step, the operands have the same integer type. The result type
is that type, and the result value is produced by applying the relevant
operation -- AND, OR, or XOR -- to each pair of corresponding bits in the input,
including the sign bit for a signed integer type.
A built-in complement operation is performed if the operand can be implicitly
converted to an integer type. The result type is that type, and the result value
is produced by flipping all bits in the input, including the sign bit for a
signed integer type. ^a is equivalent to a ^ x, where x is the
all-one-bits value of the same type as a.
A built-in bit-shift operation is performed if both operands are, or can be
implicitly converted to, integer types. The result type is the converted type of
the first operand. The result value is produced by shifting the first operand
left for << or right for >> a number of positions equal to the second
operand. Vacant positions are filled with 0 bits, except for a right shift
where the first operand is of a signed type and has a negative value, in which
case they are filled with 1 bits.
For the purposes of bit-shifts, bits are ordered by significance, with the most significant bit being the leftmost bit and the least significant bit being the rightmost bit. As a consequence, in the absence of overflow a left shift is equivalent to a multiplication by a power of 2 and a right shift is equivalent to a division by a power of two, rounding downwards.
The second operand of a bit-shift is required to be between zero (inclusive) and the bit-width of the first operand (exclusive); it is a programming error if the second operand is not within that range.
0 or -1.
In the case where the program is aborted, the program might not in all cases
be aborted immediately -- for example, multiple checks might be combined
into one -- but no control flow or memory access that depends on the value
will be performed.TODO: Unify the description of these programming errors with those of arithmetic overflow, document the behavior in a common place and link to it from here.
These operations can also be applied to a pair of integer constants, or to an integer constant and a value of integer type, as follows:
^ operator is applied to an integer constant, the
result is an integer constant. Integer constants are treated as having
infinitely many high-order bits, where all but finitely many of those bits
are sign bits. For example, -1 comprises infinitely many 1 bits. Note
that there is no difference between an arithmetic and a logical right shift
on an integer constant, because every bit always has a higher-order bit to
shift from.
1 << (1 << 1000) is thought to be substantially larger than the total
entropy in the observable universe. In practice, Carbon implementations
will set a much lower limit on the largest integer constant that they
support.&, |, or ^ operation is applied to an integer
constant and a value of an integer type to which the constant can be
implicitly converted, the operand that is an integer constant is implicitly
converted to the integer type and the computation is performed as described
above.Other operations involving integer constants are invalid. For example, a bitwise
& between a u8 and an integer constant 500 is invalid because 500
doesn't fit into u8, and 1 << n is invalid if n is an integer variable
because we don't know what type to use to compute the result.
Note that the unary ^ operator applied to a non-negative integer constant
results in a negative integer constant, and the binary ^ operator gives a
negative result if exactly one of the input operands was negative. For example,
^0 == -1 evaluates to true.
Bitwise and shift operators can be provided for user-defined types by implementing the following family of interfaces:
// Unary `^`.
interface BitComplement {
default let Result:! type = Self;
fn Op[self: Self]() -> Result;
}
// Binary `&`.
interface BitAndWith(U:! type) {
default let Result:! type = Self;
fn Op[self: Self](other: U) -> Result;
}
constraint BitAnd {
extend BitAndWith(Self) where .Result = Self;
}
// Binary `|`.
interface BitOrWith(U:! type) {
default let Result:! type = Self;
fn Op[self: Self](other: U) -> Result;
}
constraint BitOr {
extend BitOrWith(Self) where .Result = Self;
}
// Binary `^`.
interface BitXorWith(U:! type) {
default let Result:! type = Self;
fn Op[self: Self](other: U) -> Result;
}
constraint BitXor {
extend BitXorWith(Self) where .Result = Self;
}
// Binary `<<`.
interface LeftShiftWith(U:! type) {
default let Result:! type = Self;
fn Op[self: Self](other: U) -> Result;
}
constraint LeftShift {
extend LeftShiftWith(Self) where .Result = Self;
}
// Binary `>>`.
interface RightShiftWith(U:! type) {
default let Result:! type = Self;
fn Op[self: Self](other: U) -> Result;
}
constraint RightShift {
extend RightShiftWith(Self) where .Result = Self;
}
Given x: T and y: U:
^x is rewritten to x.(BitComplement.Op)().x & y is rewritten to x.(BitAndWith(U).Op)(y).x | y is rewritten to x.(BitOrWith(U).Op)(y).x ^ y is rewritten to x.(BitXorWith(U).Op)(y).x << y is rewritten to x.(LeftShiftWith(U).Op)(y).x >> y is rewritten to x.(RightShiftWith(U).Op)(y).Implementations of these interfaces are provided for built-in types as necessary to give the semantics described above.