What is the one features you would want in Zig from another language?

Did you miss the .ptr? The field .ptr doesn’t inherit immutability. I can still write to the value it points to through it, even though its parent is immutable.

1 Like

cat test.zig

fn foo(s: []const u8) void {
    s.ptr[0] = 'h';
}

pub fn main() void {
    foo("Hello");
}

zig run test.zig

test.zig:2:10: error: cannot assign to constant
    s.ptr[0] = 'h';
    ~~~~~^~~
1 Like

I can see a lot of confusion when trying to read code written like that.

4 Likes

looks to me like temporary inheritance.

You can achieve something similar at the struct level, like usingnamespace but for fields instead of declarations.
e.g.

const A = struct {
    field_1: Type1 = ...
    field_2: Type2 = ...
};

const B = struct {
    field_3: Type3 = ...
}

const C = struct {
    using A,
    using B,
};

golang call this struct composition or struct embedding, I call it inheritance.

1 Like

I like the infer idea.
Could be used without T (since you can use @TypeOf(argname) to get T) for arguments type or the return type.
e.g.

fn increment(arg: infer) @TypeOf(arg) {
}

But the semantics are problematic with the rest of the language, e.g. in increment(1) infer is replaced with comptime_int.

This could be very useful, for instance, in removing code duplication between managed and unmanaged containers in the std library. We could make the managed version contain an unmanaged one, but expose its member in the way you described, so people don’t have to do arrayList.unmanaged.items.len.

1 Like

I miss this feature too. What @tauoverpi mentioned is a different thing. @mohamed82008 was talking about what C++ has, with the auto keyword in the return type. When the comptime parameters that make up a type and its runtime values require similar logic, you end up having to duplicate code, one for the function that computes the type, and one for the function that does the runtime computations. Sadly, this was rejected on design grounds.

1 Like

No, that’s not what I meant. I completly forgot that you could use the raw pointer with .ptr and it got confusing,I’m sorry.

I meant an actual field of a struct that happens to be a pointer (not the struct, the field). I’ll make it more clear:

const std = @import("std");
const MyStruct = struct {
    index: *u32,

    pub fn incr(self: *const MyStruct) void {
        // this is legal, even though "self" isn't mutable
        self.index.* += 1;
    }
};

pub fn main() void {
    var index: u32 = 0;
    const my_struct = MyStruct{ .index = &index };
    std.debug.print("{}\n", .{my_struct.index.*});

    // Here immutable data mutates the data that it holds
    my_struct.incr();
    std.debug.print("{}\n", .{my_struct.index.*});
} 

If you zig run this you’ll get 0 then 1.

If you want the index to point to immutable value you have to declare it as:

index: *const u32,

This goes against the goals of zig though as it hides things / doesn’t tell the truth about the actual layout of the container struct. One should also prefer unmanaged variants as they don’t redundantly carry a fat pointer to the allocator thus reducing memory overhead for something you likely already have in scope.

This also makes it less clear that something is a field (quite important when you want to take a pointer to it) for someone reading the code along with which struct it belongs to. Thus, from a reading perspective, this makes things worse in a similar way to usingnamespace.

1 Like

Refactoring has two types of users: writers – those who are writing refactored code, and readers – those who then read and maintain code after refactoring.

This feature definitely makes writer’s life easier but makes code less readable. I would rather not use this refactoring trick in my code even if it were available in Zig.

2 Likes

If I don’t want it to ever be mutable when used from a MyStruct, yes!

But this is still not what I said. I’d like to have semantics for “this pointer can’t write to the data if the instance of MyStruct that holds it is immutable”. This is the feature I’d like to have in Zig.

I am understanding what you want. The problem is that you need to specify somehow the initial value of the struct fields. If it is immutable, there will be no method to set the initial value, with the current zig semantics (i.e. without special constructors).

I’m really struggling to understand what you’re saying. Maybe because English isn’t my first language? Here’s how our conversation went in my mind:

  • Me: I wish Zig had these semantics,
  • You: But it does, look!
  • Me: No, that’s not what I meant: I want these semantics, not those.
  • You: But it can’ t work, Zig doesn’t have the semantics for that.

Am I misunderstanding what you’re trying to communicate to me?

I am neither a native English speaker, and it is my fault.
Replace “I am understanding what you want.” with “Finally, I am understanding what you want.”

Lol! Now we are understanding each other, fellow non-native English speaker!

1 Like

Viral immutability will eventually cause use of @constCast which isn’t as nice or everything being passed as mutable when one doesn’t want this feature.

1 Like

function that quickly return expressions:

fn double(x: i32) i32 = x * 2;
fn Pair(comptime T: type) type = struct {
    a: T, b: T,
};
1 Like

For stateless non-member functions, I’d like to see some syntactic sugar over writing inline functions as well.

Going out on a limb here, but something that essentially compresses:

const foo = struct {
    pub fn call(x: i32) i32 { return x * 2; }
}.call;

To something like:

const foo = lambda (x: i32) i32 { return x * 2; }

Where lambda could potentially instantiate an anonymous type and return a scoped function. Personally, I don’t miss captures and I’m more than happy to write that myself - just for pure functions, this would be nice.

Of course, we have the issue of outer-scope. Should a file-level value be accessible here like it is in other places? Probably?

Then again, Zig is very sugar-free, so I strongly doubt this would get considered.

4 Likes

Already considered, initial accepted, and finally rejected: