Ir al contenido
learn

1. Instalación, v run, módulos y proyectos

Código: examples/l01_hello.v, examples/l01_script.v y el proyecto l01-project.

C# Java V
El compilador produce IL en un .dll bytecode en archivos .class código C, en un archivo temporal
Después el CLR compila el IL en tiempo de ejecución (JIT) la JVM interpreta y compila el bytecode (JIT) un compilador C convierte el C en un ejecutable nativo
Para ejecutarlo necesitas el runtime de .NET, salvo si publicas autocontenido o con Native AOT una JVM, o una imagen jlink nada: el ejecutable es nativo

V incluye un compilador C, TCC (Tiny C Compiler), en thirdparty/tcc de cada archivo de versión. TCC compila rápido y optimiza poco: V lo usa para las compilaciones del día a día, y el compilador C del sistema (gcc, clang o MSVC) para las compilaciones optimizadas con -prod. v -showcc imprime el comando del compilador C. La CI del curso muestra qué compilador construye el «Hello World» de v new, y qué tamaño tiene el resultado:

SO v . tamaño v -prod . tamaño
Windows (mi máquina) tcc.exe 281,600 bytes MSVC cl.exe 29,184 bytes
Windows (runner de CI) tcc.exe 280,576 bytes gcc (MinGW) 160,784 bytes
Linux (runner de CI) tcc.exe 385,782 bytes cc (gcc 13.3) 138,296 bytes
macOS (runner de CI) cc (Apple clang) 96,808 bytes cc (Apple clang) 126,408 bytes

En macOS, V eligió cc incluso sin -prod. En Windows, -prod usa el compilador C que encuentre: MSVC en mi máquina, que tiene las Visual Studio Build Tools y no tiene gcc, y gcc de MinGW en el runner. La lección 4 muestra por qué esa elección importa para la memoria.

La documentación recomienda compilar V desde el código fuente con git clone y make, lo que sigue la rama master. Este curso fija en cambio la versión 0.5.2 y descarga su archivo: uno por SO, con v, la biblioteca estándar, las herramientas y TCC. Lo instalo en una carpeta propia y no lo pongo en el PATH global.

Ventana de terminal
Invoke-WebRequest https://github.com/vlang/v/releases/download/0.5.2/v_windows.zip -OutFile v_windows.zip
Expand-Archive v_windows.zip -DestinationPath C:\tools # crea C:\tools\v (tardó 53 s en mi máquina)
C:\tools\v\v.exe version

Para llamarlo v desde cualquier terminal, añade C:\tools\v a tu PATH, o ejecuta v.exe symlink en una shell de administrador, como describe el README (por verificar: no lo he ejecutado).

Después, en cualquier SO:

> v version
V 0.5.2 7647ce1

v doctor imprime lo que V encontró: su carpeta, la carpeta VMODULES donde van los paquetes, y cada compilador C. En mi máquina:

|V full version |V 0.5.2 45ae01d23168b6372f734eeb38a77360bbcf184a.7647ce1
|V executable |C:\Users\spare\tools\v-0.5.2\v.exe
|VMODULES |OK, value: C:\Users\spare\.vmodules
|cc version |N/A
|gcc version |N/A
|clang version |N/A
|msvc version |N/A
|tcc version |tcc version 0.9.27 (x86_64 Windows)

v doctor dice msvc version N/A, y sin embargo v -prod encontró MSVC a través de la instalación de las Build Tools.

examples/l01_hello.v
fn main() {
println('Hello, world!')
}
> v run examples/l01_hello.v
Hello, world!
C# Java V
Compilar y ejecutar un archivo dotnet run app.cs (.NET 10) java Hello.java v run hello.v
Solo compilar dotnet build javac Hello.java v hello.v, que escribe hello.exe o hello
Compilación optimizada dotnet publish -c Release — v -prod hello.v

v run compila a un ejecutable temporal, lo ejecuta y lo borra. Las opciones del compilador van antes de run; todo lo que sigue al nombre del archivo va a tu programa.

  • fn declara una función; el tipo de retorno, si lo hay, va después de los parámetros.
  • println es una función integrada: sin import, sin Console. ni System.out..
  • Las cadenas usan comillas simples; v fmt convierte las comillas dobles en simples.
  • No hay punto y coma, y el código V se indenta con tabulaciones.

Un único archivo puede prescindir de fn main, como las instrucciones de nivel superior en C# o un archivo fuente compacto en Java 25:

examples/l01_script.v
fn greeting(name string) string {
return 'Hello, ${name}!'
}
println(greeting('C#'))
println(greeting('Java'))
Hello, C#!
Hello, Java!

${…} interpola una expresión en una cadena, como $"…{name}" en C#. El orden es el contrario que en C#, donde las instrucciones de nivel superior deben ir antes de las declaraciones de tipos: en un script V, las definiciones van primero, y una función después de la primera instrucción se rechaza.

fn greeting(name string) string {
return 'Hello, ${name}!'
}
println(greeting('C#'))
fn farewell(name string) string {
return 'Bye, ${name}!'
}
e01_script_order.v:5:1: notice: script mode started here
3 | }
4 |
5 | println(greeting('C#'))
| ~~~~~~~
6 |
7 | fn farewell(name string) string {
e01_script_order.v:7:4: error: all definitions must occur before code in script mode
5 | println(greeting('C#'))
6 |
7 | fn farewell(name string) string {
| ~~~~~~~~
8 | return 'Bye, ${name}!'
9 | }

El compilador imprime los errores en la salida de error estándar, con el archivo, la línea y la columna, y termina con el código 1.

Ventana de terminal
v new hello

v new hace tres preguntas (descripción, versión, licencia) y crea una carpeta:

hello/
├── .editorconfig
├── .gitattributes
├── .gitignore
├── main.v ← module main, fn main
└── v.mod ← como .csproj o pom.xml, pero solo metadatos
Module {
name: 'hello'
description: 'Greets people'
version: '0.0.0'
license: 'MIT'
dependencies: []
}

También ejecuta git init cuando Git está instalado. v init hace lo mismo en una carpeta existente.

Las respuestas se leen de la entrada estándar: si está cerrada, como en un script sin entrada, v new no se detiene, y escribe '<EOF>' en los tres campos. os.input devuelve esa cadena al final de la entrada (os.v, líneas 436-441), y vcreate.v solo sustituye una respuesta vacía por el valor por defecto. La CI responde con printf 'Greets people\n\n\n' | v new hello.

v run . compila todos los archivos .v de la carpeta en un solo programa, que lleva el nombre de la carpeta:

> cd hello
> v run .
Hello World!

No hay archivo de compilación que mantener: V encuentra los archivos y los módulos a partir de las carpetas. v.mod da nombre al proyecto, lista sus dependencias y marca la raíz desde la que import encuentra los módulos del proyecto.

Un módulo es una carpeta. Cada archivo .v de la carpeta empieza por module <nombre de la carpeta>, y todos ven las declaraciones de los demás; los archivos de la raíz del proyecto son module main.

l01-project/
├── v.mod
├── main.v
└── greet/
└── greet.v
greet/greet.v
module greet
// hello es público: otros módulos pueden llamarlo como `greet.hello`
pub fn hello(name string) string {
return 'Hello, ${capitalize(name)}!'
}
// capitalize es privado del módulo, como `internal` en C# o package-private en Java
fn capitalize(s string) string {
return s.capitalize()
}
main.v
module main
import os
import greet
fn main() {
names := if os.args.len > 1 { os.args[1..] } else { ['world'] }
for name in names {
println(greet.hello(name))
}
}
> v run .
Hello, World!
> v run . ada grace
Hello, Ada!
Hello, Grace!

os.args contiene la ruta del programa y después sus argumentos: os.args[1..] omite la ruta, como string[] args en C# y Java.

C# Java V
Unidad namespace, independiente de las carpetas package, una carpeta módulo, una carpeta
Importación using Greet; import greet.*; import greet
Llamada Greeter.Hello(…) Greeter.hello(…) greet.hello(…): siempre con el nombre del módulo
Público public public pub
Por defecto internal (el ensamblado) para un tipo, private para un miembro package-private privado del módulo

Sin pub, otro módulo no puede llamar a la función:

module main
import greet
fn main() {
println(greet.capitalize('java'))
}
main.v:6:16: error: function `greet.capitalize` is private
4 |
5 | fn main() {
6 | println(greet.capitalize('java'))
| ~~~~~~~~~~~~~~~~~~
7 | }

Dos reglas más de la documentación de los módulos: las importaciones no pueden ser circulares, y un módulo en a/b/ se importa como a.b pero se llama con su último nombre, b.hello().

Tarea V dotnet Java
Nueva app v new app dotnet new console mvn archetype:generate
Ejecutar una carpeta v run . dotnet run mvn exec:java
Compilar v . dotnet build mvn compile
Compilación optimizada v -prod . dotnet publish -c Release mvn package
Probar v test . dotnet test mvn test
Formatear v fmt -w . dotnet format un plugin como Spotless
Análisis estático (lint) v vet . analizadores de Roslyn Error Prone
Comprobar la instalación v doctor dotnet --info java -XshowSettings

v help <command> documenta cada comando. Las pruebas, v fmt, v vet y v doc son el tema de la lección 8, los paquetes el de la lección 12.

  • V traduce tu programa a C, y un compilador C genera el ejecutable: TCC, incluido con V, para las compilaciones del día a día; gcc, clang o MSVC con -prod.
  • v run file.v o v run . compila y ejecuta; v file.v o v . solo compila.
  • Un único archivo puede prescindir de fn main, si todas las definiciones van antes de la primera instrucción.
  • Un módulo es una carpeta; una función es privada de su módulo salvo que sea pub, y siempre se llama con el nombre del módulo.
  • v.mod contiene los metadatos del proyecto; no hay lista de archivos que mantener.
  1. Mueve fn greeting de l01_script.v debajo de las dos llamadas a println. ¿Qué dice el compilador, y por qué no funciona el mismo orden en un script?
Solución
error: all definitions must occur before code in script mode

En un archivo sin fn main, V crea un main implícito desde la primera instrucción hasta el final del archivo (modo script): una función declarada después de ese punto estaría dentro de main. En un archivo con fn main, el orden no importa: V eleva (hoisting) todas las declaraciones.

  1. Añade un módulo stats a una copia de l01-project, con una función pública word_count(s string) int, e imprime el número de palabras de los argumentos del programa.
Solución

l01-exercise:

stats/stats.v
module stats
// word_count cuenta las palabras separadas por espacios
pub fn word_count(s string) int {
return s.fields().len
}
main.v
module main
import os
import greet
import stats
fn main() {
text := os.args[1..].join(' ')
println(greet.hello('reader'))
println('${stats.word_count(text)} words')
}
> v run . V for C# and Java developers
Hello, Reader!
6 words

No cambia nada más: ningún archivo de proyecto que editar, import stats encuentra la carpeta junto a v.mod. fields divide por los espacios en blanco.

  1. Compila l01-project con v -showcc . y con v -showcc -prod .. ¿Qué compilador C usa cada compilación en tu máquina, y qué tamaño tienen los dos ejecutables?
Solución

La primera línea que empieza por > C compiler cmd: nombra el compilador. En Windows y Linux, la primera compilación usa thirdparty/tcc/tcc.exe; la compilación -prod usa gcc, cc o MSVC, el que encuentre V. En macOS, ambas usan cc. Compara tus tamaños con la tabla del principio de esta lección: el tamaño depende tanto del compilador C como de -prod, ya que en el runner de macOS la compilación -prod es la más grande.