rfcs
rfcs copied to clipboard
Definition of memory allocation for array and struct & improved initialization of variables
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.
arrayis always allocated on thestackandvectoris usually allocated on theheap(unless compiler decides to optimise? I'm not sure).arrayis fixed size and cannot grow,vectorcan grow dynamically.
We usually want struct allocated on the stack. But sometimes we also want it on the heap.
Proposal for V syntax
structis usually allocated on thestack- now you can get it allocated on the
heapwhen 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 https://github.com/vlang/v/pull/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]intwill get a allocated on the stack, and it will not be able to grow dynamically. Currently, the elements of such fixed arrayaare 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 :) :1st_place_medal: