Alternative declaration syntax

BTW, Rust is (partly) doing exactly this (let <here is implicit immut>, let mut).

Look at it this way.

The modifier acts on the mapping between a name and a value.
name <> value
pointer <> cell

What it denotes is whether rhs-item can be modified through the lhs-item.
That is to say, it marks whether lhs allows reassignment. No confusion in sight.

Your val [ro|rw] V is exactly analogous to [const | var] V.
Your *[ro|rw] T is exactly analogous to *[const|ø] T.
You just changed the names.


On proposed syntax
Nothing should intervene between the modifier and the name, because this would break grep-ability.

No, I decomposed the semantic of const/var into two absolutely independent parts:

  • give a memory cell/cells a name/type
  • indicate whether cell content is mutable or not

In C:

  • there is no special keyword for a variable
  • but there is a const modifier

In Zig:

  • there are both var and const, used to introduce name and type for a memory cell
  • const is also used as a modifier/qualifier for pointers, on the right side of the : separator

In Rust:

  • both changeable and unchangeable memory cells are introduced with let
  • but in order to be mutable, memory cell must be declared as let mut

Add imm (for ex.) keyword to Rust and this will be what I am talking about:

let mut x = 0;
let imm y = 7;
val ptr rw: *T ro = &something;

I can not see anything between ptr (name) and rw (modifier), as well as between T (type name) and ro (modifier) here, can you?

val rw ptr: *ro T;

Same here.

If we were to value consistency over convenience (less typing), I think the syntax would be:

// Variable data in memory
var x: u32 = 42;
// Constant data in memory
const y: u32 = 42;

// Variable pointer to variable data in memory
var x_ptr: *var u32 = &x;
// Variable pointer to constant data in memory
var y_ptr: *const u32 = &y;

// Constant pointer to variable data in memory
const x_ptr: *var u32 = &x;
// Constant pointer to constant data in memory
const y_ptr: *const u32 = &y;

More verbose, but symmetric, consistent, and in sync with how it’s read in English; especially the common meaning of something constant that doesn’t change, and something variable that can change (vary).

4 Likes

Yep, that was my idea
But I am talking about something else. I’m talking about single keyword both for variables and constants (because they are both names for memory cells), like Rust’s let and some special keywords for modifiers (mut/imm, rw/ro…)

In that case mem is more accurate than val:

mem rw ptr: *ro T;
mem ro ptr: *rw T;

If we were to only put qualifiers on the type:

mem ptr: rw* ro T;
mem ptr: ro* rw T;
// or
mem ptr: var* const T;
mem ptr: const* var T;
// or
mem ptr: *var const T;
mem ptr: *const var T;

just remembered this from “The Zen of Python”:

Explicit is better than implicit

Look who’s talking! :rofl:

Oh, yeah, this was one of my two variants, I just decided to use val in this imaginary syntax.

Great!!!

mem ptr: rw* ro T;

mutable pointer to permanent data, did I guess it right?

1 Like

Also think about Harvard architecture. In avr-libc there is special macro. Where would you place such an instruction for Zig?

I can imagine something like this:

mem ro,progmem x: u8 = 0x33; // list of modifiers!
val x ro,progmem : u8 = 0x33;

Constants aren’t always in memory other than the program itself though, so I don’t see how mem is more accurate. Also const T = u32; exists. There the variable and the value in it doesn’t even exist after leaving the compiler.

2 Likes

I don’t feel you at all took to heart what I wrote.
I expressed the exact same thing that you say here with *[const|ø] T. Everything else is exactly symmetric. You just provided a name for what is ø.

I’ll reiterate:
Your val [ro|rw] v is exactly analogous to [const | var] v .
Your *[ro|rw] T is exactly analogous to *[const|ø] T.

You did not decompose it into absolutely independent parts, you remapped
const -> ro,
var -> rw, and
ø -> rw.

And the addition of val is simply redundant. You can look at Rust’s let and let mut as either let [ø|mut] or [let | let mut]. The outcome is exactly the same. As a matter of fact, & and &mut are considered different types in Rust, that is, the mut here is not a qualifier, but part of the type name.

You did not change the semantics – both variants flag the lvalue as either open to assignment or no. *[const|ø] T flags the assignment operation t.* = x; as legal or not, just as [const|var] v flags v = y; as legal or not.

I see zero benefit from this apart from granting a wish for symmetry.

1 Like

And your idea that [var|const] v is essentially a reference to a memory cell in Zig is misguided. They deal with symbol declarations and they can have different meanings, like const std = @import("std") or var x: u0 = undefined. Both are legal and do not refer to any item in memory at all.

1 Like

maybe just an unpopular opinion but i like C’s function decleration more may be not from an artistic point of view, but you can see the return type of the function right away and decide if you care about it, but again most of the popular C return type is just int to let the caller know if it succedded or not, again what am i talking bout then lol.

but i think even for variables you might want to see the type first to decide “is it the thing i care about” rather than depending on the variable name and going to the end and see the variable type… just feels like C’s way of variable/function decleration can help you a little to read the code fast?

Agreed. First one is using const for type declaration, and the second… hmm :slight_smile:
But, please, do not get me wrong - I am by no way criticizing Zig syntax nor making any proposals. I just was pondering over various ways of declaring variables/constants -
originally the head post of the topic was just a comment in this topic and I had no intention to make a full-blown discussion about it.

But then @Sze considered that comment to be off topic (and it really was there)
and suggested to make a separate topic in Brainstorming category, I agreed, why not? :slight_smile:

So, my ramblings was not about Zig at all, it was an attempt to brain around various ways of declaring entities in general.

1 Like

Yes, “explicit is better than implicit”, see “The Zen of Python” again :rofl:

In C (no var keyword) everything is mutable by default.
But there is special const (true!) modifier.

In Rust everything is immutable by default.
But there is special mut (true!) modifier.
Does not matter if that mut is a part of type or not - it’a separate token.
It 's a modifier for a type (in &mut), ok?

In Zig there is no defaults, you have to use var/const explicitly.
And that’s great, but why only for the left part for declaration?
Why var ptr: *T, why not var ptr: *var T?

Let’s look again at const ptr: *const T.

What does the first const mean? The meaning is twofold, “two-in-one”, so to say:

  • to declare a name
  • to indicate that a value named by that name is immutable

What does the second const mean? Here is only one:

  • it’s a modifier/qualifier for T, that’s all

You can omit the second (thus making the pointee mutable), but you can not omit the first.

Now there is const s32 = i32, yeah. Types are values at comptime, I know. :slight_smile:
When I saw Zig for the very first time is was very unusual, btw… but I got accustomed to this very quickly.

All in all - const in Zig can have three different meanings depending on context.

Another interesting opinion: On Removing Let and Let Mut

1 Like

This is it:

Specifying whether the variable can change

  • changeable/mutable variable is a tautology
  • unchangeable/immutable variable is an oxymoron.

When they say “the variable”, they actually mean “the content of a memory cell”, don’t they?

This is about tokens order. I guess the reason why modern programming languages tend to use fn/func/proc for subroutines and let/var whatever for variables is quite “simple” - these “prefixing” keywords just make life more easy for compilers. If you see an int (I mean C), you do not know what will follow. If you see var, you definitely know what should follow next.

Tokens order, tokens order… a variable data entity/data unit declaration include (generally) these components:

  • keyword (var or let or…)
  • name
  • type
  • properties/attributes
    • mutability
    • volatileness
    • kind of memory (CPU registers, progmem)
    • something else?..
  • initializer
  • separators (like : in Pascal, Zig and many other)

It seems very logical to have a keyword to be first elevent and an initializer to be the last one.
Position of others may vary…

What is “better”,

data mut,reg ptr: * imm,progmem u8;
// mutable pointer in CPU register(s),
// pointing to immutable u8 in progmem

or

data ptr mut,reg : *u8 imm,progmem;

? I do not know :slight_smile:

1 Like