Skip to content

Commit 5193da3

Browse files
committed
langref: add basic documentation of RLS
1 parent 6b38758 commit 5193da3

File tree

1 file changed

+262
-1
lines changed

1 file changed

+262
-1
lines changed

doc/langref.html.in

Lines changed: 262 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6806,8 +6806,269 @@ fn foo() i32 {
68066806

68076807
{#header_open|Result Location Semantics#}
68086808
<p>
6809-
<a href="https://github.com/ziglang/zig/issues/2809">TODO add documentation for this</a>
6809+
During compilation, every Zig expression and sub-expression is assigned optional result location
6810+
information. This information dictates what type the expression should have (its result type), and
6811+
where the resulting value should be placed in memory (its result location). The information is
6812+
optional in the sense that not every expression has this information: assignment to
6813+
{#syntax#}_{#endsyntax#}, for instance, does not provide any information about the type of an
6814+
expression, nor does it provide a concrete memory location to place it in.
68106815
</p>
6816+
<p>
6817+
As a motivating example, consider the statement {#syntax#}const x: u32 = 42;{#endsyntax#}. The type
6818+
annotation here provides a result type of {#syntax#}u32{#endsyntax#} to the initialization expression
6819+
{#syntax#}42{#endsyntax#}, instructing the compiler to coerce this integer (initally of type
6820+
{#syntax#}comptime_int{#endsyntax#}) to this type. We will see more examples shortly.
6821+
</p>
6822+
<p>
6823+
This is not an implementation detail: the logic outlined above is codified into the Zig language
6824+
specification, and is the primary mechanism of type inference in the language. This system is
6825+
collectively referred to as "Result Location Semantics".
6826+
</p>
6827+
{#header_open|Result Types#}
6828+
<p>
6829+
Result types are propagated recursively through expressions where possible. For instance, if the
6830+
expression {#syntax#}&e{#endsyntax#} has result type {#syntax#}*u32{#endsyntax#}, then
6831+
{#syntax#}e{#endsyntax#} is given a result type of {#syntax#}u32{#endsyntax#}, allowing the
6832+
language to perform this coercion before taking a reference.
6833+
</p>
6834+
<p>
6835+
The result type mechanism is utilized by casting builtins such as {#syntax#}@intCast{#endsyntax#}.
6836+
Rather than taking as an argument the type to cast to, these builtins use their result type to
6837+
determine this information. The result type is often known from context; where it is not, the
6838+
{#syntax#}@as{#endsyntax#} builtin can be used to explicitly provide a result type.
6839+
</p>
6840+
<p>
6841+
We can break down the result types for each component of a simple expression as follows:
6842+
</p>
6843+
{#code_begin|test|result_type_propagation#}
6844+
const expectEqual = @import("std").testing.expectEqual;
6845+
test "result type propagates through struct initializer" {
6846+
const S = struct { x: u32 };
6847+
const val: u64 = 123;
6848+
const s: S = .{ .x = @intCast(val) };
6849+
// .{ .x = @intCast(val) } has result type `S` due to the type annotation
6850+
// @intCast(val) has result type `u32` due to the type of the field `S.x`
6851+
// val has no result type, as it is permitted to be any integer type
6852+
try expectEqual(@as(u32, 123), s.x);
6853+
}
6854+
{#code_end#}
6855+
<p>
6856+
This result type information is useful for the aforementioned cast builtins, as well as to avoid
6857+
the construction of pre-coercion values, and to avoid the need for explicit type coercions in some
6858+
cases. The following table details how some common expressions propagate result types, where
6859+
{#syntax#}x{#endsyntax#} and {#syntax#}y{#endsyntax#} are arbitrary sub-expressions.
6860+
</p>
6861+
<div class="table-wrapper">
6862+
<table>
6863+
<thead>
6864+
<tr>
6865+
<th scope="col">Expression</th>
6866+
<th scope="col">Parent Result Type</th>
6867+
<th scope="col">Sub-expression Result Type</th>
6868+
</tr>
6869+
</thead>
6870+
<tbody>
6871+
<tr>
6872+
<th scope="row">{#syntax#}const val: T = x{#endsyntax#}</th>
6873+
<td>-</td>
6874+
<td>{#syntax#}x{#endsyntax#} is a {#syntax#}T{#endsyntax#}</td>
6875+
</tr>
6876+
<tr>
6877+
<th scope="row">{#syntax#}var val: T = x{#endsyntax#}</th>
6878+
<td>-</td>
6879+
<td>{#syntax#}x{#endsyntax#} is a {#syntax#}T{#endsyntax#}</td>
6880+
</tr>
6881+
<tr>
6882+
<th scope="row">{#syntax#}val = x{#endsyntax#}</th>
6883+
<td>-</td>
6884+
<td>{#syntax#}x{#endsyntax#} is a {#syntax#}@TypeOf(val){#endsyntax#}</td>
6885+
</tr>
6886+
<tr>
6887+
<th scope="row">{#syntax#}@as(T, x){#endsyntax#}</th>
6888+
<td>-</td>
6889+
<td>{#syntax#}x{#endsyntax#} is a {#syntax#}T{#endsyntax#}</td>
6890+
</tr>
6891+
<tr>
6892+
<th scope="row">{#syntax#}&x{#endsyntax#}</th>
6893+
<td>{#syntax#}*T{#endsyntax#}</td>
6894+
<td>{#syntax#}x{#endsyntax#} is a {#syntax#}T{#endsyntax#}</td>
6895+
</tr>
6896+
<tr>
6897+
<th scope="row">{#syntax#}&x{#endsyntax#}</th>
6898+
<td>{#syntax#}[]T{#endsyntax#}</td>
6899+
<td>{#syntax#}x{#endsyntax#} is some array of {#syntax#}T{#endsyntax#}</td>
6900+
</tr>
6901+
<tr>
6902+
<th scope="row">{#syntax#}f(x){#endsyntax#}</th>
6903+
<td>-</td>
6904+
<td>{#syntax#}x{#endsyntax#} has the type of the first parameter of {#syntax#}f{#endsyntax#}</td>
6905+
</tr>
6906+
<tr>
6907+
<th scope="row">{#syntax#}.{x}{#endsyntax#}</th>
6908+
<td>{#syntax#}T{#endsyntax#}</td>
6909+
<td>{#syntax#}x{#endsyntax#} is a {#syntax#}std.meta.FieldType(T, .@"0"){#endsyntax#}</td>
6910+
</tr>
6911+
<tr>
6912+
<th scope="row">{#syntax#}.{ .a = x }{#endsyntax#}</th>
6913+
<td>{#syntax#}T{#endsyntax#}</td>
6914+
<td>{#syntax#}x{#endsyntax#} is a {#syntax#}std.meta.FieldType(T, .a){#endsyntax#}</td>
6915+
</tr>
6916+
<tr>
6917+
<th scope="row">{#syntax#}T{x}{#endsyntax#}</th>
6918+
<td>-</td>
6919+
<td>{#syntax#}x{#endsyntax#} is a {#syntax#}std.meta.FieldType(T, .@"0"){#endsyntax#}</td>
6920+
</tr>
6921+
<tr>
6922+
<th scope="row">{#syntax#}T{ .a = x }{#endsyntax#}</th>
6923+
<td>-</td>
6924+
<td>{#syntax#}x{#endsyntax#} is a {#syntax#}std.meta.FieldType(T, .a){#endsyntax#}</td>
6925+
</tr>
6926+
<tr>
6927+
<th scope="row">{#syntax#}@Type(x){#endsyntax#}</th>
6928+
<td>-</td>
6929+
<td>{#syntax#}x{#endsyntax#} is a {#syntax#}std.builtin.Type{#endsyntax#}</td>
6930+
</tr>
6931+
<tr>
6932+
<th scope="row">{#syntax#}@typeInfo(x){#endsyntax#}</th>
6933+
<td>-</td>
6934+
<td>{#syntax#}x{#endsyntax#} is a {#syntax#}type{#endsyntax#}</td>
6935+
</tr>
6936+
<tr>
6937+
<th scope="row">{#syntax#}x << y{#endsyntax#}</th>
6938+
<td>-</td>
6939+
<td>{#syntax#}y{#endsyntax#} is a {#syntax#}std.math.Log2IntCeil(@TypeOf(x)){#endsyntax#}</td>
6940+
</tr>
6941+
</tbody>
6942+
</table>
6943+
</div>
6944+
{#header_close#}
6945+
{#header_open|Result Locations#}
6946+
<p>
6947+
In addition to result type information, every expression may be optionally assigned a result
6948+
location: a pointer to which the value must be directly written. This system can be used to prevent
6949+
intermediate copies when initializing data structures, which can be important for types which must
6950+
have a fixed memory address ("pinned" types).
6951+
</p>
6952+
<p>
6953+
When compiling the simple assignment expression {#syntax#}x = e{#endsyntax#}, many languages would
6954+
create the temporary value {#syntax#}e{#endsyntax#} on the stack, and then assign it to
6955+
{#syntax#}x{#endsyntax#}, potentially performing a type coercion in the process. Zig approaches this
6956+
differently. The expression {#syntax#}e{#endsyntax#} is given a result type matching the type of
6957+
{#syntax#}x{#endsyntax#}, and a result location of {#syntax#}&x{#endsyntax#}. For many syntactic
6958+
forms of {#syntax#}e{#endsyntax#}, this has no practical impact. However, it can have important
6959+
semantic effects when working with more complex syntax forms.
6960+
</p>
6961+
<p>
6962+
For instance, if the expression {#syntax#}.{ .a = x, .b = y }{#endsyntax#} has a result location of
6963+
{#syntax#}ptr{#endsyntax#}, then {#syntax#}x{#endsyntax#} is given a result location of
6964+
{#syntax#}&ptr.a{#endsyntax#}, and {#syntax#}y{#endsyntax#} a result location of {#syntax#}&ptr.b{#endsyntax#}.
6965+
Without this system, this expression would construct a temporary struct value entirely on the stack, and
6966+
only then copy it to the destination address. In essence, Zig desugars the assignment
6967+
{#syntax#}foo = .{ .a = x, .b = y }{#endsyntax#} to the two statements {#syntax#}foo.a = x; foo.b = y;{#endsyntax#}.
6968+
</p>
6969+
<p>
6970+
This can sometimes be important when assigning an aggregate value where the initialization
6971+
expression depends on the previous value of the aggregate. The easiest way to demonstrate this is by
6972+
attempting to swap fields of a struct or array - the following logic looks sound, but in fact is not:
6973+
</p>
6974+
{#code_begin|test_err|result_location_interfering_with_swap#}
6975+
const expect = @import("std").testing.expect;
6976+
test "attempt to swap array elements with array initializer" {
6977+
var arr: [2]u32 = .{ 1, 2 };
6978+
arr = .{ arr[1], arr[0] };
6979+
// The previous line is equivalent to the following two lines:
6980+
// arr[0] = arr[1];
6981+
// arr[1] = arr[0];
6982+
// So this fails!
6983+
try expect(arr[0] == 2); // succeeds
6984+
try expect(arr[1] == 1); // fails
6985+
}
6986+
{#code_end#}
6987+
<p>
6988+
The following table details how some common expressions propagate result locations, where
6989+
{#syntax#}x{#endsyntax#} and {#syntax#}y{#endsyntax#} are arbitrary sub-expressions. Note that
6990+
some expressions cannot provide meaningful result locations to sub-expressions, even if they
6991+
themselves have a result location.
6992+
</p>
6993+
<div class="table-wrapper">
6994+
<table>
6995+
<thead>
6996+
<tr>
6997+
<th scope="col">Expression</th>
6998+
<th scope="col">Result Location</th>
6999+
<th scope="col">Sub-expression Result Locations</th>
7000+
</tr>
7001+
</thead>
7002+
<tbody>
7003+
<tr>
7004+
<th scope="row">{#syntax#}const val: T = x{#endsyntax#}</th>
7005+
<td>-</td>
7006+
<td>{#syntax#}x{#endsyntax#} has result location {#syntax#}&val{#endsyntax#}</td>
7007+
</tr>
7008+
<tr>
7009+
<th scope="row">{#syntax#}var val: T = x{#endsyntax#}</th>
7010+
<td>-</td>
7011+
<td>{#syntax#}x{#endsyntax#} has result location {#syntax#}&val{#endsyntax#}</td>
7012+
</tr>
7013+
<tr>
7014+
<th scope="row">{#syntax#}val = x{#endsyntax#}</th>
7015+
<td>-</td>
7016+
<td>{#syntax#}x{#endsyntax#} has result location {#syntax#}&val{#endsyntax#}</td>
7017+
</tr>
7018+
<tr>
7019+
<th scope="row">{#syntax#}@as(T, x){#endsyntax#}</th>
7020+
<td>{#syntax#}ptr{#endsyntax#}</td>
7021+
<td>{#syntax#}x{#endsyntax#} has no result location</td>
7022+
</tr>
7023+
<tr>
7024+
<th scope="row">{#syntax#}&x{#endsyntax#}</th>
7025+
<td>{#syntax#}ptr{#endsyntax#}</td>
7026+
<td>{#syntax#}x{#endsyntax#} has no result location</td>
7027+
</tr>
7028+
<tr>
7029+
<th scope="row">{#syntax#}f(x){#endsyntax#}</th>
7030+
<td>{#syntax#}ptr{#endsyntax#}</td>
7031+
<td>{#syntax#}x{#endsyntax#} has no result location</td>
7032+
</tr>
7033+
<tr>
7034+
<th scope="row">{#syntax#}.{x}{#endsyntax#}</th>
7035+
<td>{#syntax#}ptr{#endsyntax#}</td>
7036+
<td>{#syntax#}x{#endsyntax#} has result location {#syntax#}&ptr[0]{#endsyntax#}</td>
7037+
</tr>
7038+
<tr>
7039+
<th scope="row">{#syntax#}.{ .a = x }{#endsyntax#}</th>
7040+
<td>{#syntax#}ptr{#endsyntax#}</td>
7041+
<td>{#syntax#}x{#endsyntax#} has result location {#syntax#}&ptr.a{#endsyntax#}</td>
7042+
</tr>
7043+
<tr>
7044+
<th scope="row">{#syntax#}T{x}{#endsyntax#}</th>
7045+
<td>{#syntax#}ptr{#endsyntax#}</td>
7046+
<td>{#syntax#}x{#endsyntax#} has no result location (typed initializers do not propagate result locations)</td>
7047+
</tr>
7048+
<tr>
7049+
<th scope="row">{#syntax#}T{ .a = x }{#endsyntax#}</th>
7050+
<td>{#syntax#}ptr{#endsyntax#}</td>
7051+
<td>{#syntax#}x{#endsyntax#} has no result location (typed initializers do not propagate result locations)</td>
7052+
</tr>
7053+
<tr>
7054+
<th scope="row">{#syntax#}@Type(x){#endsyntax#}</th>
7055+
<td>{#syntax#}ptr{#endsyntax#}</td>
7056+
<td>{#syntax#}x{#endsyntax#} has no result location</td>
7057+
</tr>
7058+
<tr>
7059+
<th scope="row">{#syntax#}@typeInfo(x){#endsyntax#}</th>
7060+
<td>{#syntax#}ptr{#endsyntax#}</td>
7061+
<td>{#syntax#}x{#endsyntax#} has no result location</td>
7062+
</tr>
7063+
<tr>
7064+
<th scope="row">{#syntax#}x << y{#endsyntax#}</th>
7065+
<td>{#syntax#}ptr{#endsyntax#}</td>
7066+
<td>{#syntax#}x{#endsyntax#} and {#syntax#}y{#endsyntax#} do not have result locations</td>
7067+
</tr>
7068+
</tbody>
7069+
</table>
7070+
</div>
7071+
{#header_close#}
68117072
{#header_close#}
68127073

68137074
{#header_open|usingnamespace#}

0 commit comments

Comments
 (0)