Definition of memory allocation for array and struct & improved initialization of variables
avitkauskas opened this issue · comments
Note: This may be not very well defined RFC.
Taking a chance while the template is not provided yet :)
Location in memory
We want some constructs to be located on the stack
and others located on the heap
.
We want to have control. Language should make some guarantees about that.
Some languages have array
and vector
(sometimes also a slice
) to distinguish between these two.
array
is always allocated on thestack
andvector
is usually allocated on theheap
(unless compiler decides to optimise? I'm not sure).array
is fixed size and cannot grow,vector
can grow dynamically.
We usually want struct
allocated on the stack
. But sometimes we also want it on the heap
.
Proposal for V syntax
struct
is usually allocated on thestack
- now you can get it allocated on the
heap
when declaring a variable:
stuct S {
x int
}
s := &S{}
&
also indicates a reference in V and is a bit confusing here.
What if we use @
instead to denote the heap
allocation?
s := @S{}
This would also allow us to have the following syntax for array
and vector
:
array
- fixed size, located on the stack
a := [0].repeat(3)
a := [1, 2, 3]
vector
- can grow, located on the heap
a := @[0].repeat(3)
a := @[1, 2, 3]
a := @[] // empty vector on the heap
This contradicts with current syntax when now we only have vector
documented in the language (though called array in documention), so this should change. Contra-argument here could be that vectors
are much more popular, so their syntax should be simpler, but then it does not match the memory allocation idea with the struct
(most stucts we want on the stack
and most arrays we want of the heap
?)
This syntax allows us to avoid constructs like a := [3]int(1, 2, 3)
or even worse a := [3]int
which compiles now but leaves a
uninitialized.
The idea of enforcing developer to provide the initial value for every variable is nice. But this is not strictly followed with struct
. As now we do:
struct S {
x int
y int
n string
}
s := S{n: 'whatever'}
and struct
members x
and y
are initialized with the default
values (decided by the compiler, not the developer). This is not consistent. If structure members can be initialized by default
values, then why not stand-alone variables? Then why we cannot do x := int
and let it be initialized to 0
as it is done in the struct
?
To be consistent, we should be declaring the struct
as follows:
struct S {
x := 0
y := 1
n := ''
}
Then it would make perfect sense and all default
values would be explicitly provided by developer.
But the current struct
syntax looks more clear, you can see the types of the members more easily. Though, if we are comfortable to understand the types of all variables from their initial values, so we should also be comfortable to see the types of the struct
members from the initial values.
Proposal for compromise
What if we join explicit types with optional initial values and clearly defined default initial values?
We would get the full definition syntax like that:
a : int = 0
with type or initial value optional.
If you omit the type, you provide your initial value:
a := 1
It you omit the value, you get clearly defined default value:
a : int
assert(a == 0)
Then you could define stuct
with the default values like that:
struct S {
x : int
y : int
n : string
}
It would also be consistent with the usage of :
when initializing a variable:
s := S{y: 2}
We could also allow this:
s1 := S{}
s2 := s1{y:2}
I don't know how it relates to "one way of doing things", but overall this approach is very clear and consistent.
This would also urge us to clearly define default
values for all the types:
Default values
bool -> false
string -> ''
i8 i16 int i64 i128
byte u16 u32 u64 u128
-> 0
rune // represents a Unicode code point
-> ` ` // How shold we write Unicode literals in V
// what should be a default value? u\0000?
// we do not have `char`, shoud we? is `byte` for that?
f32 f64 -> 0.0
byteptr -> null // should it be allowed?
voidptr -> null // should it be allowed?
// why we have 2 types of pointers?
// should it be just `ptr` and the exact type infered from initial value?
// like: `p : &byte = &'some string'`
If we say V is safe language, then can we allow null
? But if we do not allow it, how should we live without it? Definitelly using Option
, but this should also be looked into seriously. Option
handling today still faces some questions.
// why we have 2 types of pointers?
I think the answer for this (byteptr and voidptr) is easy interoperability with existing C code.
The language already implements so called fixed arrays, for example
a := [5]int
will get a allocated on the stack, and it will not be able to grow dynamically. Currently, the elements of such fixed array a
are not initialized (but probably should) to 0.
In the future, you would be able to do:
(as @medvednikov commented below vlang/v#2241)
a := [3]int([1,2,3])
... which will also allocate a on the stack, and initialize its elements with the numbers 1 2 3.
The current syntax for this fixed array initialization is:
a := [1, 2, 3]!!
Other than the uninitialized yet fixed arrays, v variables already do get initialized to their default values, which are 0 (their implementation is such, that filling with zeros the memory of a struct or an array or a string for example 'initializes' everything correctly).
The language already implements so called fixed arrays, for example
a := [5]int
will get a allocated on the stack, and it will not be able to grow dynamically. Currently, the elements of such fixed arraya
are not initialized (but probably should) to 0.In the future, you would be able to do:
(as @medvednikov commented below vlang/v#2241)
a := [3]int([1,2,3])
... which will also allocate a on the stack, and initialize its elements with the numbers 1 2 3.The current syntax for this fixed array initialization is:
a := [1, 2, 3]!!
I know that, and that's exactly what I am not happy about:
1.- We have two very different syntaxes:
a := [1, 2, 3] // dynamically allocated on the heap
and
a := [3]int([1,2,3]) // fixed size, on the stack, and quite ugly :(
2.- Also, the syntax for fixed size is inconsitent with other definitions: from the docs "Array type is determined by the first element: [1, 2, 3] is an array of ints ([]int)." All the types are inferred from the values, except fixed arrays? Why do we need int
in the fixed array definition? That's why I propose a := @[1, 2, 3]
- it's consistent with dynamic arrays.
Only I propose to switch it ([1, 2, 3]
- fixed, @[1, 2, 3]
- dynamic - and it's only to be consistent with struct
, as for the struct
we usually want in on the stack.
// why we have 2 types of pointers?
I think the answer for this (byteptr and voidptr) is easy interoperability with existing C code.
Yes, then we could have:
p := null // voidptr in C, same as just ptr in V
p : ptr // same as obove
p : &byte // byteptr initialized with default `null`
a := 'abc'
p := &a // byteptr with the address of `a`
b := 0.5
p := &b // &f32, same as *float in C
p : &f64 // *double
Wouldn't this allow to have all type of pointers you need, both for V and for C interop?
Thanks @avitkauskas
1.- We have two very different syntaxes:
a := [3]int([1,2,3])
is just a := T(val)
, like any declaration. We simply specify that it's a fixed size array.
null
is for C code only.
I'll cover the rest of the points after I wake up :)
I'll cover the rest of the points after I wake up :)
🥇