Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discussion: Array/Slice typechecking #568

Closed
Hejsil opened this issue Oct 27, 2017 · 8 comments
Closed

Discussion: Array/Slice typechecking #568

Hejsil opened this issue Oct 27, 2017 · 8 comments
Milestone

Comments

@Hejsil
Copy link
Contributor

Hejsil commented Oct 27, 2017

One of the best simple changes Zig gives us over C is the ability to have actual arrays/slices (not just pointers) in our code. However, after playing with these arrays, I found that the type checking did not make sense to me. I hope that this issue will either:

  • Explain exactly what the rules are for slices and arrays in the type system (Hopefully even get this information in the documentation).
  • Or, if it needs to change, discuss what should change.

Ok, so let me explain why I have a problem understanding the type system behind arrays and slices. First, the documentation doesn't seem to explain the use of const in types such as pointers and slices. This means, I kinda have to go by what I think it means, which is: []const i64 means a slice, whose elements cannot be changed or a slice of const i64.
With this in mind, let's go use arrays and slices.

// A slice, whose elements can be changed
var slice: []i64 = undefined;

// A slice, whose elements can't be changed
var sliceOfConstElements: []const i64 = undefined;

// An array, whose elements can be changed
var arrayOfSize: [4]i64 = undefined;

// An array, whose elements can't be changed
// NOTE: This will not compile.
// var arrayOfSizeWithConstElements: [4]const i64 = undefined; // error: const qualifier invalid on array type

slice[0] = 1;
sliceOfConstElements[0] = 1; // error: cannot assign to constant
arrayOfSize[0] = 1;
// arrayOfSizeWithConstElements[0] = 1;

Ok, so far so good. I seems I'm not totally misunderstanding it since sliceOfConstElements[0] = 1; gave an error. I wonder why we can't have arrays of constant elements though...

Next, we ofcourse want to be able to assign our variables to values, instead of undefined:

var slice: []i64 = []i64{ 1, 2, 3, 4 }; // error: expected type '[]i64', found '[4]i64'
var sliceOfConstElements: []const i64 = []i64{ 1, 2, 3, 4 };
var arrayOfSize: [4]i64 = []i64{ 1, 2, 3, 4 };

Ooh, so I can't assign a slice to an array literal? But I can if the slice is const? Why? My best guess is, that apparently [4]i64 actually implicitly is const. Why not call the type [4]const i64?
Well, ok. Let's test something then. Can I declare const in the literal?

var slice: []i64 = []const i64{ 1, 2, 3, 4 }; // error: expected type '[]i64', found '[4]i64'
var sliceOfConstElements: []const i64 = []const i64{ 1, 2, 3, 4 };
var arrayOfSize: [4]i64 = []const i64{ 1, 2, 3, 4 };

I can? Ooook? So what is the difference between []const i64{ 1, 2, 3, 4 } and []i64{ 1, 2, 3, 4 }?

// Test 1/1 Array literal types...reached unreachable code
test "Array literal types" {
    if (@typeOf([]i64{ 1, 2, 3 }) == @typeOf([]const i64{ 2, 4, 6 })) {
        unreachable;
    }
}

No difference? So I guess the compiler does not respect const in array literals.

I could go on with my story format like this, but I think you all get that this is confusing me a little. Here is a bigger piece of code, that outlines more things that confuse me:

fn useSlice(slice: []i64) { }
fn useSliceConst(slice: []const i64) { }

pub fn main() -> %void {
    var   varArray        = []i64 { 1, 2, 3 };
    const constArray      = []i64 { 1, 2, 3 };
    var   varArrayConst   = []const i64 { 1, 2, 3 };
    const constArrayConst = []const i64 { 1, 2, 3 };

    var   varArrayExplicit       : []i64 = []i64 { 1, 2, 3 }; // error: expected type '[]i64', found '[3]i64'. Why?
    const constArrayExplicit     : []i64 = []i64 { 1, 2, 3 }; // error: expected type '[]i64', found '[3]i64'. Why?
    var   varArrayConstExplicit  : []const i64 = []i64 { 1, 2, 3 }; // No error? Why?
    const constArrayConstExplicit: []const i64 = []i64 { 1, 2, 3 }; // No error? Why?

    var   varArrayExplicitLiteralConst       : []i64 = []const i64 { 1, 2, 3 }; // error: expected type '[]i64', found '[3]i64'
    const constArrayExplicitLiteralConst     : []i64 = []const i64 { 1, 2, 3 }; // error: expected type '[]i64', found '[3]i64'. Ok, so we can see here, that the const declartion does not make the type const
    var   varArrayConstExplicitLiteralConst  : []const i64 = []const i64 { 1, 2, 3 };
    const constArrayConstExplicitLiteralConst: []const i64 = []const i64 { 1, 2, 3 };

    // 
    // Reassign
    //
    varArray        = []i64 { 2, 4, 6 };
    constArray      = []i64 { 2, 4, 6 }; // error: cannot assign to constant
    varArrayConst   = []i64 { 2, 4, 6 }; // No error? Why? Does the compile no respect the const in []const i64{ ... }?
    constArrayConst = []i64 { 2, 4, 6 }; // error: cannot assign to constant

    // I guess these all make sense
    varArray        = []i64 { 2, 4 }; // error: expected type '[3]i64', found '[2]i64'
    constArray      = []i64 { 2, 4 }; // error: cannot assign to constant
    varArrayConst   = []i64 { 2, 4 }; // error: expected type '[3]i64', found '[2]i64'
    constArrayConst = []i64 { 2, 4 }; // error: cannot assign to constant

    varArray        = []const i64 { 2, 4, 6 }; // No error? Why?
    constArray      = []const i64 { 2, 4, 6 }; // error: cannot assign to constant
    varArrayConst   = []const i64 { 2, 4, 6 };
    constArrayConst = []const i64 { 2, 4, 6 }; // error: cannot assign to constant

    // I guess these all make sense
    varArray        = []const i64 { 2, 4 }; // error: expected type '[3]i64', found '[2]i64'
    constArray      = []const i64 { 2, 4 }; // error: cannot assign to constant
    varArrayConst   = []const i64 { 2, 4 }; // error: expected type '[3]i64', found '[2]i64'
    constArrayConst = []const i64 { 2, 4 }; // error: cannot assign to constant

    //
    // Assign element
    // 
    varArray       [0] = 5;
    constArray     [0] = 5; // error: cannot assign to constant. Why? I guess const declared arrays have const elements?
    varArrayConst  [0] = 5; // No error? Why?
    constArrayConst[0] = 5; // error: cannot assign to constant

    //
    // Func taking non const element slice
    //
    useSlice(varArray); // error: expected type '[]i64', found '[3]i64'. Why no implicit convertion to slice? 
    useSlice(constArray); // error: expected type '[]i64', found '[3]i64'. If implicit convertion happend here, this probably still shouldn't work, because of the implicit const elements.
    useSlice(varArrayConst); // error: expected type '[]i64', found '[3]i64'. This should still give error after an implicit convertion to slice.
    useSlice(constArrayConst); // error: expected type '[]i64', found '[3]i64'. This should still give error after an implicit convertion to slice.
    useSlice([]i64{ 1, 2, 3 }); // error: expected type '[]i64', found '[3]i64'. Should this be allowed if we have implicit slice convertion?
    useSlice([]const i64{ 1, 2, 3 }); // error: expected type '[]i64', found '[3]i64'. This probably shouldn't.

    //
    // Func taking const element slice
    //
    // So if we mark a func to take const element slices, then we get the implicit convertion to slice always?
    useSliceConst(varArray);
    useSliceConst(constArray);
    useSliceConst(varArrayConst);
    useSliceConst(constArrayConst);
    useSlice([]i64{ 1, 2, 3 }); // No error? Isn't this a type missmatch?
    useSlice([]const i64{ 1, 2, 3 });
}
@andrewrk
Copy link
Member

    var   varArrayConst   = []const i64 { 1, 2, 3 };

This should be "error: const qualifier invalid on array literal".

If you think about it, the memory of the array is contained within the varArrayConst variable, which is declared var. So those array bytes are variable.

For a slice, the initial var or const describes whether you can reassign the slice local variable, and then the second optional const refers to the pointer of the slice.

The way to think about this stuff is with pointers.

When you have a pointer, one of the properties of a pointer is whether it is const or not. A slice is a pointer and a length, and the const-ness of the slice is exactly the const-ness of the pointer.

When you declare a local variable, the memory for that local variable is on the stack. If you take a pointer to that local variable, the const-ness of the pointer is whether or not the local variable is const or var.

    var   varArrayExplicit       : []i64 = []i64 { 1, 2, 3 }; // error: expected type '[]i64', found '[3]i64'. Why?
    const constArrayExplicit     : []i64 = []i64 { 1, 2, 3 }; // error: expected type '[]i64', found '[3]i64'. Why?
    var   varArrayConstExplicit  : []const i64 = []i64 { 1, 2, 3 }; // No error? Why?
    const constArrayConstExplicit: []const i64 = []i64 { 1, 2, 3 }; // No error? Why?

Zig won't implicitly take a mutable reference to something, but it will implicitly take a const reference to something.

I'll make the fixes and update the docs before closing this issue, and if you have any other questions, I'll try to answer them.

@Hejsil
Copy link
Contributor Author

Hejsil commented Oct 28, 2017

if []const i64 { 1, 2, 3 } is an error, then that clears up a lot of confusion already. So to confirm that I understand:
const is not part of the type system except for in pointers (slices are pointers with a length). That is why we can't have [4]const i64, because there is no pointer. So arrays have the same behavior as structs. Structs can't have const fields. I would then also assume that you can't pass arrays to function by value:

const SomeStruct = struct { v1: i64, v2: i64, v3: i64 };
fn takeStruct(s: someStruct) { } // error: type '[4]i64' is not copyable; cannot pass by value
fn takeArray(a: [4]i64) { } // error: type 'someStruct' is not copyable; cannot pass by value

Ok, so does const pointers then work the same as const slices (Implicit cast from value to pointer)?

const SomeStruct = struct { v1: i64, v2: i64, v3: i64 };

fn takeStruct(s: &const SomeStruct) { }
fn takeArray(a: []const i64) { } 

pub fn main() -> %void {
    var   varArray   = []i64 { 1, 2, 3 };
    const constArray = []i64 { 1, 2, 3 };
    var varStruct : SomeStruct = undefined;
    var constStruct : SomeStruct = undefined;
    takeArray(varArray);
    takeArray(constArray);
    takeArray([]i64 { 1, 2, 3 });
    takeStruct(varStruct);
    takeStruct(constStruct);
    takeStruct(SomeStruct{ .v1 = 1, .v2 = 2, .v3 = 3 });
}

It does! Ok, so it all is consistent and I just misunderstood. Thx for clearing it all up.

@andrewrk
Copy link
Member

I appreciate you filing this issue. These are the little details I want to get right. I'll be sure to add the missing error and clear up the docs before closing the issue.

@andrewrk andrewrk added this to the 0.3.0 milestone Nov 2, 2017
@Hejsil
Copy link
Contributor Author

Hejsil commented Nov 16, 2017

One other quick thing, which I should probably have mentioned as it was one of the sources of confusion. Arrays/Slices in arrays, which in initializers are kinda weird because of inferred array size.

pub fn main() -> %void {
    //           slice
    //           |--|
    var fun1 = [][]u8 { "1", "2" }; // Error
    //         |----|
    //          array

    //           slice
    //            |--|
    var fun2 = [2][]u8 { "1", "2" }; // Error
    //         |-----|
    //          array

    //           array
    //           |---|
    var fun3 = [][1]u8 { "1", "2" };
    //         |-----|
    //          array

    //            array
    //            |---|
    var fun4 = [2][1]u8 { "1", "2" };
    //         |------|
    //          array
}

In type signatures i never have to guess as to whether [] is an inferred array or a slice.

pub fn main() -> %void {
    //           array      array
    //           |---|      |---|
    var fun3 : [][1]u8  = [][1]u8 { "1", "2" }; // Error
    //         |-----|    |-----|
    //          slice      array

    //            array      array
    //            |---|      |---|
    var fun4 : [2][1]u8 = [2][1]u8 { "1", "2" };
    //         |------|   |------|
    //          array      array
}

@thejoshwolfe
Copy link
Contributor

Currently []T{a, b, c, ...} is array initialization syntax, but as noted above this is confusing since []T is a slice not an array.

Proposal: change array initialization syntax to [_]T{ ... } where the keyword _ means that this is an array not a slice, and the length is omitted. The type [_]T would only be allowed in special circumstances, such as array initialization.

@andrewrk andrewrk modified the milestones: 0.3.0, 0.2.0 Jan 8, 2018
@andrewrk andrewrk modified the milestones: 0.2.0, 0.3.0 Feb 28, 2018
@andrewrk andrewrk modified the milestones: 0.3.0, 0.4.0 Jul 18, 2018
@andrewrk andrewrk modified the milestones: 0.4.0, 0.5.0 Mar 20, 2019
@andrewrk
Copy link
Member

@Hejsil is it fair to say that there are open issues to address all of your discussion points in this issue?

@andrewrk andrewrk modified the milestones: 0.5.0, 0.6.0 Apr 30, 2019
@Hejsil
Copy link
Contributor Author

Hejsil commented May 1, 2019

@andrewrk If there is an issue for having inferred size of array be [_]u8{0, 1, 2}, then yes.

@andrewrk
Copy link
Member

andrewrk commented Jun 9, 2019

@andrewrk If there is an issue for having inferred size of array be [_]u8{0, 1, 2}, then yes.

OK great, so this issue is covered by other open issues then. And the specific one mentioned above is now done (#1797)

@andrewrk andrewrk closed this as completed Jun 9, 2019
@andrewrk andrewrk modified the milestones: 0.6.0, 0.5.0 Sep 29, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants