| ... | ... | @@ -6806,8 +6806,269 @@ fn foo() i32 { |
| 6806 | 6806 | |
| 6807 | 6807 | {#header_open|Result Location Semantics#} |
| 6808 | 6808 | <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. |
| 6810 | 6815 | </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#} |
| 6811 | 7072 | {#header_close#} |
| 6812 | 7073 | |
| 6813 | 7074 | {#header_open|usingnamespace#} |