-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Welcome to my vlang-lessons-learnt wiki!
This repo is really only about documenting my experience with V-lang, gotchas, things to remember etc.. The information are in no particular order. I plan to gain a bit of experience with V first, before raising issues with the core developers. Mostly to avoid asking stupid questions.
-
"\0"
prints a warning that 0 chars are not allowed in strings (for easy C interoperability reasons I assume) - however
"\000"
(otcal representation) works fine - (now available)
"\x00"
, the"\x<hex>"
notation is not supported - (now available)
"\u<uuuu>"
for unicodes is also not supported
I think V is a bit confused about C-style \0
terminated strings and V-strings (len attribute). I fully understand that V's core needs to interact a lot with C-libs and C-interoperability should be easy for core and lib-developers. But should users care? Either V-string are C-style and \0
terminates a string, but then we need a []byte that does not. Or the len attribute is used and \0
has no special meaning. By means of special functions, e.g. from_cstring(), to_cstring() it might be handled. A CString struct probably requires a lot of source code copy & paste, since V has no String-interface which would allow for multiple different implementations.
if _ := fn_that_may_throw_an_error { assert false }
It also works with if/else like this, and the err
value is also available.
res := ""
if res = my_fn_that_raises_an_error() {
// ok path
} else {
res = another_function(err)?
}
- 'return' usually means "return from a function", but in an 'if' and 'match'-expression it is not allowed
fn my_test() ? {
a := if x == 1 {
"test"
} else {
return error("..")
}
...
}
This does not work as expected: the function will not return with an error. Instead the V-compiler will complain.
Replacing "test"
with a_function()?
will be accepted by the V-compiler, but the generated C-code will not compile.
Instead you need to do something like:
fn my_test() ? {
mut a := ""
if x == 1 {
a = "test"
} else {
return error("..")
}
...
}
V supports modules and for an application it seems to recommended to put them into a ./modules
subdirectory of the application. For a re-useable module, that you may want to use in multiple application, may be not the best place to put it.
V also has vpm, a package manager, and you can register / upload your module their, which, however makes it public, which you may want or may be not, if it is meant to be private. When downloading a module via vpm, it gets cached in ~/.vmodules. Vpm seems to have no option to register a module only locally, without uploading.
The v.mod
file has a dependencies
field, which can be filled with the names of the dependent modules. Directories however are not supported.
This offers the following option
- Use git sub-projects for every module to allow that each module has it's own git-repo. Not my favorite
-
..\v\v.exe -path "@vlib;../vlang-mmap/modules"
to update the search path for modules - Create a soft-link in ~/.modules to point to the directory where your module resides.
- Create a soft-link in your ./modules directory to point to the directory where your module resides.
I'm currently using the last one.
v.mod: I haven't tested what will happen if you create a soft-link in .vmodules and then do an vpm upgrade
.
Win10 now supports soft-links as well: e.g. mklink /d $HOMEPATH/.vmodules/mmap $MY_VLIBS\modules\mmap
Also looking at the modules already registered in VPM you see that all modules have their source code in the main directory. After some weeks of developing the mmap and yaml modules, I don't like that very much. I prefer to have source code in some 'src' directory. In the root folder they are mixed with all sort of other files (v.mod, readme.md, .gitignore, and so on).
I'm also yet undecided whether I like source code and test files (and test data) in the same directory. My tendency is more to have it separate. I haven't checked, but what happens to test data when building the .exe file? E.g. in case of the YAML module, I have plenty test data files located in a subdirectory? Unfortunately the docs is not really clear on this.
What I also don't like is that you can have only one executable per directory. You need a 'main' module, and all *.v files in the same directory are considered part of the module. You must use a directory per executable strategy. Which also generates the executable in the that directory. I would prefer to generate them in the ./bin folder. The only way to achieve this right now: create your own little build-script that copies the files. I think I mentioning it already elsewhere, but V has no build-system with pre- and post-processors etc.. It basically is just the compiler.
I like the python approach to use negative indexes such as ar[-10 ..]
, and more generically, when using a range to automatically
make sure that lower and upper boundaries are properly adjusted if needed. For performance reasons, V-lang doesn't have it. Instead
you need to do ar[.. math.max(0, ar.len - 10)]
or similar.
In that context, math.max() and friends seem to have issues with casting the return type. It seems they always
return f64 so that you actually need to write: ar[.. u64(math.max(0, ar.len - 10))]
for it to work.
Update: Since recently, V-lang supports ar#[-10 ..]
. Please note the extra #
which tells the V-compiler to use
another range implementation. An implementation that is similar to Python's approach, and always returns a slice. The
slice might be empty though. Please see the V-lang docs for more details. I'm undecided yet whether I like the extra '#'.
As mentioned in one of the other entries, I like to think of operators as syntax-sugar for (core) functions. Unfortunately
V-lang has no such core functions for either of the range types. Which also means, that ranges are not extensible. They
are only available for (core) types which are hard-coded in the compiler.
V-lang has an assert
statement, but unfortunately it does not support an optional error message, such as
assert my_fn() == 0, "Expected xyz to provide whatever: $1 != $2"
assert
also does not support multiple return values, e.g. `assert 1, 2 == 1, 2
assert
is probably not ready yet, e.g. src := "abced"; assert byte(30) == src[0]
does not work. Assert will report
the right value as "unknown value". Whereas if you do src := "abced"; x := src[0]; assert byte(30) == x
it will do.
I had a function that was causing a divide-by-zero runtime error. I first didn't know because even though
the result said that a test failed, no output was printed as it usually happens when a test fails. I was confused
until I found the -stats
option (e.g. v -stats test .
) which prints a lot more info. And in my case, it also
printed the stack trace with the divide-by-zero error. => remember the -stats
option when v test .
fails but
does not print any output.
While on tests. I find it unfortunate that v test
has not cli option to stop test execution after the first failure.
Sometimes you make a breaking change and you need to review and fix all the test failures. Which you usually do one
after the other.
V internal structs such as string, bytes etc. are lowercase snake_case. Whereas, the examples in the documentation for user created structs is CamelCase. That is not consistent.
I like constants to be all UPPERCASE. Not recommended or possible in V.
With import xyz
you can import a module. But you must fully qualify the function in the source code like xyz.my_fn()
,
which I like. And I fully agree that it makes it easier to read the code. Unfortunately V also has import xyz { my_fn }
which imports my_fn() into the namespace of my current module. It does not require a fully qualified name upon its use.
I developed a little memory mapping modul, because we needed to read files which are >10 GB. This is not possible with the built-in
string or []byte types, as there len variable is an int
which in V is 32bit. Which means strings can not have more then 2GB
chars. We developed our own large_string module with an i64 len field.
Unfortunately V has no convention which allows to use [ .. ]
with their structs. It is currently hard-coded in V. A convention,
such as ar[a .. b]
which get translated in ar.slice(a, b)
would be a simple approach to achieve this.
V comes with a source code formatter, which is a good idea. I'm all fine with it putting brackets where they belong etc., but I do not like that it moves and even destroys some of my comments. E.g.
struct X {
x int /* = 0 */ // This field ...
}
In that context, I also don't like that I'm not allowed to use system default as default value for a variable. V prints a warning, which, when generating production code with -prod
will fail with an error message. It make the code more readable to add defaults, even when it is a system default.
Unfortunatly V doesn't seem to have conventions for implementing certain syntactic sugar, e.g.
ar[a .. b] to ar.range(a, b)
, or ar[i] to ar.at(i)
or ar << x to
ar.add(x)`.
V seems to have a next() convention for iterators, but it's kind of half only. E.g. struct string
has no field for
tracking the position in the iterator, but string does work in for s in "abc" {}
. Somehow magically some iterator
struct for string must be instantiated. In user created structs there is no such magic. You need to create a struct
that has a next() method. E.g. for x in MyIterator{ data: my_data }
. A convention could be that an iter()
function
gets invoked. so that for line in file
gets translated into:
mut iter := file.iter()
for {
if line := iter.next() {
...
} else {
break // End of iterator
}
}
Similarly for for i, line in file
Or like below, which I think makes it even easier to create iterators. But it requires
an iterator
attribute and yield
keyword. Again the compiler would simply replace
for line file.line_iter()
pretty much with the iterator function body. An advantage,
compared to python, would be that exceptions are not silently swollowed, and the overhead
is virtually zero.
[iterator]
pub fn (file FWFile) line_iter() ?string {
for i in 0 .. file.records {
line := file.line(i)?
yield line
}
}
div-by-zero, invalid-array-index, etc. cause a panic and do not return an error. Unfortunately it is not possible to test that, as panics can not be caught, not even in test code. I understand some sort of recover is planned, but currently not available.
if obj is []int { eprintln(obj.len) }
is not working. It'll complain that obj
has no len
function.
I encountered this issue when trying to define something like:
type YamlValue = map[string]YamlValue | []YamlValue | string
I was positively surprised that V allows to use the type (recursively) within its own definition. But because of the smart-cast issue I had to create structs for the map and the list like
struct YamlListValue { ar []YamlValue }
struct YamlMapVakue { obj map[string]YamlValue }
type YamlValue = YamlMapValue | YamlListValue | string
Let's assume struct MyStruct {..}
then, in the absence of constructors, you may be tempted to use
fn new_MyStruct() MyStruct {..}
as a convention. Unfortunately that doesn't work with V. V is strict with upper-case
in struct names and lower-case in function names :(
A typical struct looks like
struct MyData {
this_is_private int
pub:
this_is_public_immutable int
pub mut:
this_is_public_mutable int
mut:
this_is_private_mutable int
}
Observations are:
- it is not possible to use any of ´put` access mutators multiple times. This forces the dev to put the variable into groups.
- There is no
private
. It always must come first
I'm undecided whether I like this or not. Usually I group my vars by purpose, which makes the source code more readable. But may be that is an old C++/Java experience, where structs or classes tend to be large. In V, the struct definition is usually not that long.
I'm more concerned with data placement, when the exact offset, length and padding is relevant for reading and writing binary data structures. Placement is not supported right now anyways, but I don't see how it could.
Struct field which are private mutable and public immutable (read-only)? imho, enough adding [pub-read-only] attribute...
To safe me writing, I occassionaly want a reference to an array stored in a struct. The trap is that 'mut ar := mystruct.myarray' creates a copy. I wish V would raise a warning, as this is usually not what you want. V's map seem to have move()
and copy()
functions, which I think is a good idea as it makes the intend obvious.
Raising that question on the help channel, the answer was "don't do it" (create a reference). Well, I thought, they certainly have more experience then me, and I created a function that receives a mutable array like fn my_fn(mut ar []int)
. Fine, problem solved, I thought. Strangely the function did not compile. In short
struct MyData1 { pub mut: ar []int }
fn pass_array_mut(mut ar[]int) int {
if ar > 0 && ar.last() == 99 { return 99 }
return 0
}
fn test_pass_array() {
mut m := MyData1{}
m.ar << 99
assert pass_array_mut(mut m.ar) == 99
}
Looking at the C code then a ptr to array is provided, but the code produced for ar.last() assumes it is an array (not a ptr). It looks like the C-code generated for ar.last() (or any array function?) is wrong.
Imagine some code like: if rtn is YamlListValue { rtn = rtn.ar[p] }
and a compiler error message like "The error message may be field 'ar' does not exist or have the same type in all sumtype variants". The error message is correct, not all sumtype variants have an 'ar' field. But that is why I'm using smart cast in the first place. The solution is simple, but the error message is giving no hint in that direction: if mut rtn is YamlListValue { rtn = rtn.ar[p] }
. Just add mut
to the if-statement. My suggestion to the V-team: improve the error message.
I wanted to create a function (attached to struct) with the name 'match'. Which currently is not possible, as 'match' is a keyword in Vlang. The compiler will complain. Unfortunately VLang doesn't have a proper grammar file right now (like Zig for example), which IMHO would help remove uncertainty.
I wanted to add a CLI tool to my app, and I thought 'cli' is a good name for a sub-module under the main module, e.g. 'rosie.cli'. Unfortunately that doesn't work right now. Even though it's only a submodule, the compiler complains, reporting that 'cli' is already a module name. Which is true, vlib comes with a cli module, but in a different parent module. Vlang currently ignore the context (parent) and just compares the last name.
I like Julia's and NIM's metaprogramming capabilities and ways of doing it, which is safe, compared to C macros. It allows to generate V-code at compile time. This is useful e.g. for
- regular expression compilation at compile time. Many REs are not that complicated and very efficient code could be generated
- Rosie is more advanced pattern matching library. The rosie files must be compiled, like source code. V's build process is not extendable. You can not trigger a pre-build step, like with a build tool.
- File reader (e.g. JSON, YAML, etc.) would benefit from auto-generating V-code matching the file structure
- Similarly ORM components. V's build-in ORM is nice for scripts and very simple apps. I wish it would be possible to generate V-code that matches the underlying database structure. Whether it reads the DB's metadata or uses some other sort of config is not important.
V uses attributes to support mapping of json data, but as of today, it is built-in and not extensible in any form.
I like how some other languages have f".."
or r".."
or .. and that it simply is syntactic should for some function, e.g. f_str(..)
, r_str(..)
Occassionaly I read about []! and []!!, which is not documented anywhere. I think it has something todo with where the array gets allocated: static, stack, heap, but that is only a guess
Consider:
type YamlTokenValueType = string | i64 | f64 | bool
fn (obj YamlTokenValueType) str() string { return "xxx" }
A bit surprisingly for me, this seems to be working. Which is a pleasant surprise. But I don't think it is documented, hence I'm not sure it is planned or by accident.
See (here)[https://github.com/vlang/v/issues/10898) for additional details.
.. while you defined str() methods on the sumtype variants, you never defined a custom str() method on the sumtype itself. By default sumtypes (and interfaces, type aliases, etc.) are always printed as a cast, so the behavior you're seeing is working as intended.
If you do not want the sumtype to be printed as a cast for whatever reason, you need to define a custom str() method on the sumtype itself.
IMHO Vlang is not consistent here. type_name() only consists for sumtype (may be also interfaces, I don't know). But simple struct don't. For structs you shall use typeof(obj).name, and obj.type_name() raises a compiler error.
Also see here
This is copy and paste from the help channel, so that I don't forget about it.
You can do: ./v -profile x.txt run examples/path_tracing.v
. Please note that -profile does currently not work with *_test.v files!! After your program finishes, open the x.txt
in a text editor, it will have something like this in it:
64403 15.769ms 245ns strings__new_builder
2 0.001ms 452ns strings__Builder_write_ptr
22 0.008ms 359ns strings__Builder_write_b
385209 152.446ms 396ns strings__Builder_write_string
64403 34.304ms 533ns strings__Builder_str
64403 8.111ms 126ns strings__Builder_free
The first column is the number of invocations of each function, the second is the cumulative time spent in each function, the 3rd is the average time (2nd / 1st), and the 4th is the C name of the function. Another way (which is linux specific afaik) is to use valgrind
- compile your program: ./v -cc gcc-10 -g -keepc examples/path_tracing.v
- run it under valgrind: valgrind --tool=callgrind ./examples/path_tracing
- that will produce a callgrind.out.PID file
- run kcachegrind callgrind.out.PID
kcachegrind
offers a very nice interactive way to visualise the data - you can sort by various metrics, focus on specific functions etc
so if you use linux, I highly recommend it.
Heaptrack
is also a nice tool, if you are looking to optimise memory usage
- compile your program (same as before)
- heaptrack ./examples/path_tracing
- that will produce a heaptrack.path_tracing.PID.zst file
- visualise it with heaptrack --analyze heaptrack.path_tracing.PID.zst
afaik, it is also a linux only tool
As of writing this entry, I'm using 'V 0.2.2 5452ba4', which is the latest. So far I'm mostly ok with V, but I would rate the compiler maturity currenty 'alpha'. Be prepared for unexpected compiler crashes, generated C-code that doesn't compile, workarounds because of bugs, edge cases not working, thin or incomplete documentation, inconsistencies, etc.. This sounds more negativ than it is. The community is usually supportive, and V users are currently probably mostly people that like coding and fully aware of the status of V-lang. I guess the V core team is realizing that it takes more time then expected. Originally they planned to be production ready around Christmas 2019.
On this topic: upgrade V doesn't work on Win10. Neither 'v up' nor 'make'. I always have to delete the V folder and re-install from scratch. UPDATE: This seems fixed now. I use 'make -tcc' on Windows.
Argh. I didn't create a test case for the V-devs. My fault. I had it ones that the program compiled fine but ran into a panic. I remember it was in a function returning a struct or an error. The program paniced when I tried to access the struct in a return stmt. After a while I figured that the function producing the return value, did not return anything in a very specific situation. It took me some time, but then I figured that the function which had several if-then-else statements, did not return anything (no value and no error) in one specific case. Ups. I fixed it and it worked (and I forgot about it :( ). The point is: the compiler didn't complain.
When you create a struct with a ref variable, e.g. struct Ax { b &Bref }
then the compiler complains upon initialising an Ax
if you don't provide a reference. That is very good. Now consider struct MyStruct { a Ax }
. When you do m := MyStruct{}
, implicitely an 'Ax{}' is created, and in this situation the compiler does not complain. Instead a NULL reference is created :( , and will have an unpleasant surprise (panic) later on,
Apologies, this one will be a little longer. The use case (problem) first: The rosie compiler needs to generate byte code for different pattern, e.g. char, string, charset, groups and aliases. Rosie is a pattern language and supports multipliers (?, +, *, {n,m}) and predicates (<, >, !). There is a generic byte code pattern that can be applied for predicates and multipliers. Some pattern however benefit from optimized byte code. I seek a V-lang supported design pattern, that delivers easy to read, easy to maintain (no copy & paste) and flexible to modify source code. E.g. in a first implementation, it is fine for every rosie pattern to generate byte code for the generic pattern. Later, and step by step, optimized byte code will be generated for individual rosie patterns.
In Java I would design an abstract base class, which implements the logic for the generic pattern. And concrete subclasses for the specialisations. These concrete subclasses would first be empty, inheriting everything from the base class. The abstract base class typically consists of a single entry method, which calls severals other object methods, which may again call other methods. Which allows subclasses to remain small and clean, by overriding only the few methods needed, to generate the optimized code. If the generic code must be adjusted, you only need to modify the abstract base class. Very easy, readable, maintainable and yet flexible.
I know that V-lang has no inheritance, so that very design pattern cannot be applied. But what would be a V-lang compliant design pattern, that delivers source code which has equally good attributes.
My current V-lang approach uses separate structs per rosie pattern. Attached methods implement the business logic to generate the byte code. The caveat are: it involves copy & paste, and every change to the generic code portions must be carefully copied to all other structs.
I don't want to be picky or nasty, but I thought I write down just in case ...
I think below is a good example where V's syntax could be a bit improved.
mut ar := rosie.libpath.clone()
ar << os.join_path(home, "rpl")
return ar
I personally like [..libpath, "whatever"],
but libpath.clone().add("whatever")
would also be ok. The latter actually fails, because V has issue to detect or make the clone() result mutable, so that another value can be added.
I can't say I especially like the tweak to use structs for named function parameters. Yes, for me it is a tweak. I would prefer the V-lang syntax spec to be improved to natively support named parameter.
That it's more of a tweak then a feature is also evident by the [params]
(compiler) attribute required to allow for empty parameter lists.
NB: the [params] tag is used to tell V, that the trailing struct parameter can be omitted entirely, so that you can write button := new_button(). Without it, you have to specify at least one of the field names, even if it has its default value, otherwise the compiler will produce this error message, when you call the function with no parameters: error: expected 1 arguments, but got 0.
Maps have fairly recently been added to V-lang. Unfortunately V's assert statement does not yet support it. E.g. assert mymap["abc"] == 123
will print *unknown value*
for the left part if the assertation does not match. But that is not my main point. My main point is that V-lang has no generic means to extend how values are printed. Some interface that structs might implement and which features such as asserts (and possibly others) are leveraging. May be str()
which is already used for string interpolation (formatting).
Another gotcha with V assertations: Something like assert "net" in p.main.imports
will basically end in an endless loop. The issue seems related to the in
operator in combination with maps. Add extra brackets such as assert ("net" in p.main.imports)
and it works.
It is nice and easy to build or run a single executable or shared library, but V has nothing to help with release management. E.g. my little vrosie project has:
- A cli executable
- A shared lib (*.so or *.dll)
- A python module is on my todo list, which might end up in a pyvrosie.so file or something
- May be I want to create an rpm package and publish it
- Create the documentation (may be in different formats: html, man pages, ...)
- Run absolutely all tests to make sure everything is working fine
- A build.v file, which builds, tests and installs all the components whenever a binary package is not available
- Properly tags the revision in git
See https://github.com/vlang/v/issues/12411 for more details. In summary, you cannot do
mut iter := data.my_filter()
for x in iter {
eprintln("x: $x => $iter.pos") // (1)
}
V will make a copy of iter
and update the copy. Currently my only solution is: manually craft the loop yourself
IMHO V-lang is a little inconsistent regarding the use of ?
for calling functions that return an optional. What do I mean with that:
-
x := my_function()?
=> the function returns an optional. If the return value is an error, then return from the current function and pass it on to the parent function.?
means "pass the error on" -
x := my_function() or {..}
=> The error will be handled by the 'or' block. No?
-
if x := my_function() {..}
=> If an error, then continue with the 'else' block. No?
-
return my_function()
=> Whatever my_function returns, pass it on the parent. No?
A simple explanation could be: whenever the optional gets handled within the function, than no ?
. If the optional should be passed on to the parent, then use ?
. The only one that doesn't fit is return
. IMHO ?
should be required in the return context.
But there is more: accessing map values. E.g.
-
a := mymap["x"]
will return either the value found or the default value of the map value type. It will not panic or throw an error => IMHO this should be fixed -
a := mymap["x"]?
will return an error if not found -
a := mymap["x"] or { 123 }
is also fine to provide a default value
I wish the V's syntax would be more consistent here.
I raised a feature request to support "stop test execution after first failure". This is now available, though not yet documented. I personally would also like to see an easy to use command line option.
With latest V 7b72326, you can now do:
VJOBS=1 VTEST_FAIL_FAST=1 ./v test . .
or
VJOBS=1 ./v test -fail-fast .
Doing just ./v test -fail-fast .
also works, but may not do what you expect, since if you do not set VJOBS
explicitly to 1 (sequential execution), then all the tests, that were already started in parallel to the one that failed, will still continue their execution.
We can also make -fail-fast
set VJOBS
to 1 implicitly, if that is what people want, at the cost of making the 2 options slightly interdependent.
A very easy example. Any ideas on what might go wrong here?
struct Inner { str string }
struct Outer { inner Inner }
fn (o Outer) inner() &Inner { return &o.inner }
fn test_ptr() ? {
outer := Outer{}
eprintln("${voidptr(&outer)}: ${voidptr(&outer.inner)} == ${voidptr(outer.inner())}")
assert &outer.inner == outer.inner()
}
Running this test actually succeeds. You may use v -stats
to print the output. And you'll be surprised: the printed values for voidptr(&outer.inner)
and voidptr(outer.inner())
are different?? How can the assertation succeed, if the references are different?
Well, the assertion does not compare the pointers, but it is a V-lang feature that pointers are automatically de-referenced. Which means that the assertion compares the struct values and not the pointers. You can easily validate this by reviewing the generated C-code v -stats -keepc -showcc -cg ..
.
Ok, so I changed the assertion to assert ptr_str(voidptr(&outer.inner)) == ptr_str(outer.inner())
and now it fails. That is good and bad. Good, because we are now able to reproduce the problem, and bad, because we still don't know where things go wrong.
A closer look at the generated C-code for fn (o Outer) inner() &Inner { return &o.inner }
makes we wonder. The function body copies the receiver o
struct?!?!. It doesn't need to, but obviously it is because the receiver is not declared with mut
and neither is it explicitely a pointer. The solution: either of the following changes makes the example work as envisioned.
fn (mut o Outer) inner() &Inner { return &o.inner }
fn (o &Outer) inner() &Inner { return &o.inner }
Since in my case it doesn't need to be mutable, I choose the 2nd approach.
My lesson learnt: in doubt immutable means also for structs (not just literals) "make an implicit copy". It doesn't mean "you can easily pass around a reference, as it is guaranteed that it will never be modified"
Quite a nasty issue, and certainly I'll watch out for in the future.
An interesting idea mentioned in one of the V Discord channels. Unfortunately V-lang is not supporting it.
My little Rosie lib allows to combine different parsers, optimizers, compilers and runtimes. Initially I played with different instruction sets for the runtime virtual machine, then a core-0 parser and an RPL based parser, then different RPL dialects requiring additional parsers, and so on. I leverage V's module systems by creating a component directory, e.g. ./parser, and a subdirectory per parser. When I start with a new component, I often copy & paste something existing. Occassionally I forget to update the 'module ...' declarations. The V-compiler will complain of course and usually the root cause is obvious, but it made me think: why do we need the 'module' statement? Why not leverage the directory name as module name. What is the additional value of being able to have multiple different modules within the same directory? Some people may argue that rules for directory names are dependent on the Operating System and the rules are different from module name rules (all lowercase etc.). But directory names can be checked as well. I'm uncertain.
V's naming convention suggests 1-letter receiver names, e.g. fn (p Parser) my_fn()
. I don't have a strong opinion after some time coding a V project, but I slightly prefer python's self
. To me, it makes it more obvious.
Strangely the syntax is const ( .. )
and not const { .. }
. Vlang is usually using curly brackets for blocks. Except when it comes to const-block, where they are using parenthesis.
I've stumbled upon this issue more then once. Let's say you have an if
or match
statement (with assignement), then you can not return from within these blocks. The compiler throws an error upon the return statement. The solution is to first declare the variable and assign to it in each switch block. But like below it is not always nice. Initialising a "PatternCompiler" is potentially expensive.
fn (mut c Compiler) compile_elem(pat rosie.Pattern, alias_pat rosie.Pattern) ? {
mut be := match pat.elem {
rosie.LiteralPattern { PatternCompiler(StringBE{ pat: pat, text: pat.elem.text }) }
none { return error("Pattern not initialized !!!") }
}
be.compile(mut c)?
}
You can define raw string literals like r"\n"
. Raw means that what is in between the quotes will not be unescaped but is taken verbatim. r"\n" == "\\n"
. It is not a big thing, but ocassionaly it makes the code (string) more readable, which is good. Unfortunately raw string literals are not consistently allowed where string literal are possible. E.g. the following declaration of a map with string key values, raises a compiler error message:
x := {
r"ab": 1
r"bc": 2
}
A little unexpected to me, the following works: An expression in match
m := rand.intn(100)
x := match true {
m == 0 {'blah'}
m == 1 {'brah'}
m < 10 {'less then 10'}
else { 'error' }
}
In my little project I needed something like:
$for e in SymbolEnum {
symbols.symbols << e.name
}
Unfortunately that is not (yet) available
I like this article about structured concurrency. And I did read V's answer, which basically is "Go has plenty such libs and you can build such a library in V as well". Which is true.
I like this article because it explains how programming evolved from goto to flow control statements and (black box) functions, and that this improved quality, re-use, error handling, testing, readability etc.. Then it goes on to make a similar case for go-like statements and structured concurrency, and how structured concurrency helps to avoid many common concurrency issues.
It is true, a V-lib could be developed. Which I think misses the point a bit. If structured concurrency helps to avoid common concurrency bugs and improves code quality, then it should be the default and promoted in V's documentation as an improvement over Go. Currently V is marketing that is has a goto statement (sorry, a go statement).
since recently V has generics. But arrays and maps are using an old syntax. For consistency reasons, I think it should be changed.
I fully support his comments: https://github.com/vlang/v/discussions/7610#discussioncomment-4784873