The V Programming Language: A Comprehensive Textbook Guide
Welcome to the ultimate learning guide for the V programming language! This textbook is structured specifically to take you from a complete beginner (zero programming experience) to an advanced V developer capable of building high-performance, concurrent, and safe systems applications. Rather than treating V as a list of syntax rules, this guide emphasizes a practical path: learn the core ideas, run the examples, and build small projects as you go.
How to read this book: Each section starts with a clear explanation of a fundamental programming concept, followed by concrete V code examples. Every example contains the exact code from the repository, formatted in clean code blocks so you can easily copy and run them yourself.
Interactive Learning: You can test any code example from this guide live in your browser using the V Playground.
Repository Structure
This book is paired with a topic-based repository layout so it is easier to explore examples by concept. The structure is intentionally arranged in a learning sequence rather than as a flat list of files:
variables_and_constants/,primitive_types/, andcontrol_flow/for the foundation of the languagefunctions/andstructs/for building reusable programs and modeling dataerror_handling/,modules/, andtesting/for reliability and organizationconcurrency/,channels/,json_and_orm/,sqlite/, andnotes_api/for real-world applicationslanguage_updates_and_stdlib/for newer language features and grouped standard library examples that are easier to browse by topic
Following this structure makes it simpler to move from small examples to larger projects, and it also gives contributors a clear place to add new lessons.
A better way to think about the repo
- Start with the introductory folders when you are learning V for the first time.
- Use the middle sections when you are ready to write more structured programs.
- Explore the application-oriented folders once you want to build something practical.
Contributing new content
When adding a new lesson, keep it in the most relevant topic folder and use a numbered naming pattern such as 01_topic_name/ so the learning flow stays predictable.
Quick Start: Learn V by Building Things
If you are new to programming, the fastest way to learn V is to start small and build something real. Follow this sequence:
- Install V and confirm it works with
v --version. - Create a file named
hello.vwith a tiny program:
fn main() {
println('Hello, V!')
}
- Run it with
v run hello.v. - Build an executable with
v -o hello hello.v.
V has a few ideas that are worth remembering early:
- Variables are immutable by default, so use
mutwhen you need to change a value. - Modules help organize larger programs.
optionandresultmake error handling explicit.spawnand channels make concurrency approachable.
Core Language Essentials to Learn Early
A beginner-friendly roadmap becomes much clearer when you call out the core concepts that show up again and again in V programs:
mutand immutable-by-default variables- functions, parameters, and return values
- structs and methods for modeling data
enumandmatchfor branching on discrete choices- modules and imports for organizing code
?and!for option and result typesdeferfor cleanup work before a function exitsunsafeonly when you truly need low-level access- type aliases and sum types once you are comfortable with structs and enums
These ideas form the backbone of most V programs, so it is worth learning them in a small, practical order rather than trying to memorize every feature at once.
Must-Learn-Before-Building Checklist
Before you start a larger project, make sure you can comfortably do the following:
- write a small program with a
mainfunction - declare variables and explain when to use
mut - define and call functions with clear parameters and return types
- model simple data with structs
- choose between
if,match, andforfor control flow - split code into modules and import them correctly
- handle optional or failing values with
?and! - use
deferfor cleanup work when needed - avoid
unsafeunless you truly need it
If you can do these reliably, you are ready to move from tiny examples to real programs.
Why This Matters
The goal is not just to memorize syntax. Each concept in this guide solves a real programming problem:
- Variables and mutability help your program store and update information safely.
- Functions let you break a program into small, reusable pieces.
- Structs help you model real-world data such as users, files, or payments.
- Error handling makes programs more predictable and easier to debug.
- Concurrency helps programs do more work efficiently when tasks can run independently.
When you learn a new feature, ask yourself: “What problem does this solve?” and “How would I use it in a small program?”
Suggested Learning Path
A beginner-friendly path through this guide is:
- Start with Chapters 1-4 to learn the basic syntax and control flow.
- Move into functions, structs, and modules to structure your programs.
- Practice with tests and error handling before tackling larger projects.
- Finish with concurrency, JSON, and databases by building a small app.
Next-Level Language Features to Explore
Once the basics feel natural, the next step is to expand your comfort with a few higher-level features:
- type aliases for clearer naming
- sum types for values that can be one of several shapes
- generics for reusable data structures and helpers
- interfaces for shared behavior across different types
- higher-order functions and function values
unsafeand pointers only when you need low-level performance or interop
These features are not required for every beginner project, but they become very useful once you start writing more structured or reusable code.
Mini Projects to Try
These projects will make the guide feel much more practical:
- A command-line to-do list
- A number guessing game
- A simple file organizer or text search tool
- A notes app that stores data in JSON or SQLite
- A macOS desktop app with a native Cocoa UI: vlang_simplegui
- A macOS desktop app with a webview-based UI: vlang_macos_webview_app_template
Practice Exercises
Try these small exercises as you move through the guide:
- Write a program that prints your name and age.
- Create a function that adds two numbers and returns the result.
- Build a tiny program that stores a user in a struct and prints the fields.
- Write a loop that prints the first 10 even numbers.
- Use an
optionorresultin a small helper function and handle the failure case.
If you get stuck, write the smallest possible version first and test it before adding more features.
Your First Project: A Tiny CLI Greeting App
A great first project is a small command-line app that asks for a name and prints a greeting. This lets you practice variables, functions, input, and output without getting overwhelmed.
Step 1: Start with a simple main function
fn main() {
println('Hello, V!')
}
Step 2: Add a name variable
fn main() {
name := 'Ada'
println('Hello, ' + name + '!')
}
Step 3: Make it interactive
import os
fn main() {
name := os.input('What is your name? ')
println('Hello, ' + name + '!')
}
Step 4: Improve it with a function
import os
fn greet(name string) string {
return 'Hello, ' + name + '!'
}
fn main() {
name := os.input('What is your name? ')
println(greet(name))
}
Why this project is useful
This project teaches the core flow of programming in V:
- write a small program
- test it
- add a feature
- refactor it into functions
- make the output clearer
Once this feels easy, you can extend it with a command-line option, a loop, or a saved history file.
What Most Programmers Want Next
A strong guide should help readers move from learning syntax to building and debugging real software. The following topics are especially useful for most programmers:
Quick Reference
- Run a file:
v run hello.v - Build an executable:
v -o hello hello.v - Use
mutwhen a value needs to change - Use functions to keep logic organized
- Use modules to split larger projects into manageable files
- Use tests to verify behavior as you grow your program
Common Beginner Mistakes
- Forgetting to make a variable
mutbefore changing it - Mixing up declaration and assignment
- Writing code without small, testable functions
- Trying to learn too many concepts at once instead of building one small feature
Debugging and Reading Errors
When something fails, focus on the first compiler error, reduce the problem to a smaller example, and test one change at a time. This is often faster than changing many lines at once.
Real-World Workflow
As your projects grow, you will want to know how to:
- split code into modules
- write tests
- structure folders clearly
- read documentation and standard library examples
- move from small scripts to larger applications
From Practice to Real Projects
Once the basics feel comfortable, the next step is to build small applications that combine multiple ideas. A very effective progression is:
- Build a tiny CLI tool that reads input and prints output.
- Add functions, structs, and tests.
- Introduce modules so the code is easier to maintain.
- Add file I/O or JSON handling for persistence.
- Explore concurrency for tasks that can run in parallel.
This progression helps learners move from “I can read examples” to “I can build useful software.”
Where to Go Next
After finishing this guide, the best next steps are:
- read the official V documentation and examples
- try the V Playground for rapid experimentation
- build one project end to end instead of reading only
- contribute to or study real V repositories for idiomatic patterns
Beginner Project Roadmap
A practical roadmap for new V developers could look like this:
Milestone 1: CLI App
Build a small command-line app that accepts user input and prints useful output. This helps you practice functions, variables, and control flow.
Milestone 2: Data App
Add file I/O or JSON handling so your app can save and load data. This introduces practical patterns for real-world applications.
Milestone 3: Structured App
Split the app into modules and add structs for data models. This is where programs become easier to maintain.
Milestone 4: Tested App
Write tests and improve reliability. This is an important step for building confidence as a programmer.
Milestone 5: Concurrent App
Explore concurrency with spawn and channels for tasks that can run in parallel. This is where V becomes especially compelling for performance-oriented software.
Setup Checklist
Before you start coding in V, make sure you have:
- V installed and available on your terminal
- a text editor or IDE with basic syntax highlighting
- a way to run and test small programs quickly
- a folder for practice files and mini projects
Quick Glossary
mut: makes a variable changeablefn: defines a functionstruct: defines a custom data typemodule: groups related code togetheroption/result: explicit ways to handle missing or failing valuesspawn: runs code concurrentlychannel: passes data between concurrent tasks
How to Use This Guide Effectively
To get the most from this book:
- Read the explanation first, but do not stop there.
- Run each example locally.
- Change one small thing and observe what happens.
- Write your own tiny version before moving on.
- Apply each idea in a small project as soon as possible.
- Chapter 1: Getting Started with V
- Code Comments
- Chapter 2: Variables and Constants
- Constants
- Variables
- Chapter 3: Primitive Data Types
- Primitive Types Demo
- Boolean Type
- Numeric Types
- Rune Type
- String Type
- Chapter 4: Control Flow
- Control Flow Extras
- Chapter 5: Collections: Arrays and Maps
- Arrays
- Maps
- Chapter 6: Functions
- Advanced Function Features
- Function Extras
- Chapter 7: Structs (Custom Types)
- Struct Basics & Fields
- Chapter 8: Error Handling
- Option & Result Types
- Chapter 9: Organizing Code with Modules
- Modules & Project Structure
- Installing External Packages
- Chapter 10: Writing Tests in V
- Assertions & Unit Testing
- Chapter 11: Concurrency and Channels
- Channels & Communication
- V-Routines & Concurrency
- Chapter 12: Working with Databases and JSON
- Case Study: Notes API
- JSON & ORM
- SQLite Integration
- SQLite CRUD Helper
- Sqlite Raw Crud
- Chapter 13: Standard Library & Advanced Features
- Inline Assembly & C Interop
- Networking (TCP, UDP, SSL, WebSockets)
- Other Stdlib Updates
- Strings.Lorem Helper
- WebAssembly Compilation
- Chapter 14: Useful Boilerplates and Application Templates
- CLI Command-Line Application Boilerplate
- REST API Server Boilerplate
- Worker Pool Concurrency Boilerplate
- OS and File Utilities Boilerplate
- String Utilities Boilerplate
- Math and Statistics Boilerplate
- Array Utilities Boilerplate
- Chapter 15: Comprehensive Practice Exercises
- Practice Exercises Overview
Chapter 1 Getting Started with V
Quick Access
Below is an index of all code examples in this chapter. You can use these links to jump directly to any specific code example:
Code Comments
This chapter introduces the core design philosophies of V. You will learn how to set up your development environment, compile and run programs, and document your code using comments.
Code Comments
Single Line Comments
Single Line Comments
Comments are non-executable lines of text in a program that explain what the code does. They are ignored by the compiler but are essential for human developers. This lesson on Single Line Comments demonstrates how to write and format comments in V.
Additional Context from Repository docs:
This example demonstrates the concepts of single line comments.
module main
// greet function prints greetings to the console
pub fn greet() {
println('Hello, Welcome to the Jungle!')
}
fn main() {
greet()
}
Multi Line Comments
Multi Line Comments
In V, multi-line (or block) comments are enclosed between /* and */.
Nested Block Comments: Unlike languages like C, C++, Java, or JavaScript, V supports nested block comments. This is a powerful feature that allows you to easily comment out large blocks of code even if they already contain block comments, without triggering syntax errors.
Additional Context from Repository docs:
This example demonstrates the concepts of multi line comments.
module main
/*
multiply is a function that accepts two integer arguments (x and y).
It performs multiplication and returns the integer product.
/*
Note: In V, block comments can be nested.
This is a nested block comment. In standard C, nesting block comments
would cause a compile error, but V's compiler parses them correctly.
*/
This is the end of the outer block comment.
*/
fn multiply(x int, y int) int {
return x * y
}
fn main() {
println(multiply(4, 5))
}
Programm Commented All Places
Programm Commented All Places
Comments are non-executable lines of text in a program that explain what the code does. They are ignored by the compiler but are essential for human developers. This lesson on Programm Commented All Places demonstrates how to write and format comments in V.
Additional Context from Repository docs:
This example demonstrates the concepts of programm commented all places.
module main
// Space3D A struct indicating the 3 dimensional coordinate system
struct Space3D {
mut:
x int
// x is an integer field that represents coordinate
y int
// y is an integer field that represents coordinate
z int
// z is an integer field that represents coordinate
}
/*
get_point is a function that returns a struct of Type Space3D with points x,y,z passed as input arguments to it
x is an input argument accepts values of type of int
y is an input argument accepts values of type of int
z is an input argument accepts values of type of int
get_point function returns a Struct result of type Space3D with its coordinates set as value passed as input arguments x, y and z
*/
fn get_point(x int, y int, z int) Space3D {
return Space3D{
x: x
y: y
z: z
}
}
const origin = get_point(0, 0, 0)
// Defining origin as a constant
fn main() {
// origin := Space3D {x: 0, y: 0, z:0}
println(origin)
}
Chapter 2 Variables and Constants
Quick Access
Below is an index of all code examples in this chapter. You can use these links to jump directly to any specific code example:
Constants
- Define Single Constant
- Define Multiple Constants
- Define Constant Of Type Struct
- Define Constant Of Type Function
- Define Module Level Constants
- Cannot Define Constants Inside Functions
- Constants Module - Main (main.v)
- Constant Module Prefix - Helper (file1.v)
Variables
- Parallel Declaration Immutable Variables
- Parallel Declaration Mutable Variables
- Parallel Declaration Mut And Immutable Vars
- Augmented Assignment String
- Augmented Assignment Integer
- Declare Mutable Variable
- Cannot Update Mutable With Another Type
- Declare Immutable Variable
- Cannot Update Immutable Variables
- Declared And Assigned
- Declared And Not Assigned
- Unused Variables Will Be Warned
- Global Variables Not Allowed - Scope Demo
- Global Variables Not Allowed - File Scope Demo
- Variable Redeclaration
- Variable Scope For Same Variable Names
- Variable Shadowing Not Allowed
Variables are the basic storage units of any program. In this chapter, we explore how V handles variables with a safety-first mindset: variables are immutable by default, variable shadowing is forbidden, and constants are declared in module scopes. You will learn to manage program data safely and cleanly.
Constants
Define Single Constant
Define Single Constant
Constants in V are defined using the const block. Constants are values that are known at compile time and never change throughout the execution of the program. By convention, constant names are written in lowercase, unlike many other languages.
This example shows how to define and use a single constant.
Additional Context from Repository docs:
This example demonstrates the concepts of define single constant.
const app_name = 'V on Wheels'
fn main() {
println(app_name)
}
Define Multiple Constants
Define Multiple Constants
You can define multiple constants within a single const block. This keeps related constants grouped together and makes the code cleaner.
This example shows how to declare multiple constants (integers, strings, floats) together.
Additional Context from Repository docs:
This example demonstrates the concepts of define multiple constants.
const app_name = 'V on Wheels'
const max_connections = 1000
const decimal_places = 2
const pi = 3.14
fn main() {
println(app_name)
println(max_connections)
println(decimal_places)
println(pi)
}
Define Constant Of Type Struct
Define Constant Of Type Struct
Variables and constants store state in V programs. This lesson on Define Constant Of Type Struct covers declaration rules, default values, scopes, or constant naming conventions.
Additional Context from Repository docs:
This example demonstrates the concepts of define constant of type struct.
module main
struct Space3D {
mut:
x int
y int
z int
}
const origin = Space3D{
x: 0
y: 0
z: 0
}
fn main() {
println(origin)
}
Define Constant Of Type Function
Define Constant Of Type Function
Variables and constants store state in V programs. This lesson on Define Constant Of Type Function covers declaration rules, default values, scopes, or constant naming conventions.
Additional Context from Repository docs:
This example demonstrates the concepts of define constant of type function.
module main
struct Space3D {
mut:
x int
y int
z int
}
fn get_point(x int, y int, z int) Space3D {
return Space3D{
x: x
y: y
z: z
}
}
const origin = get_point(0, 0, 0)
fn main() {
println(origin)
}
Define Module Level Constants
Define Module Level Constants
Variables and constants store state in V programs. This lesson on Define Module Level Constants covers declaration rules, default values, scopes, or constant naming conventions.
Additional Context from Repository docs:
This example demonstrates the concepts of define module level constants.
module main
const app_name = 'V on Wheels'
fn main() {
println(app_name)
}
Cannot Define Constants Inside Functions
Cannot Define Constants Inside Functions
Variables and constants store state in V programs. This lesson on Cannot Define Constants Inside Functions covers declaration rules, default values, scopes, or constant naming conventions.
Additional Context from Repository docs:
This example demonstrates the concepts of cannot define constants inside functions.
module main
const app_name = 'V on Wheels'
fn main() {
const greet = 'hi' // this is not top level constant definition, throws error.
println(app_name)
}
Constants Module - Main (main.v)
Constants Module - Main
Variables and constants store state in V programs. This lesson on Main covers declaration rules, default values, scopes, or constant naming conventions.
Additional Context from Repository docs:
This example demonstrates the concepts of main.
module main
import mod1
fn main() {
mod1.do_work()
}
Constant Module Prefix - Helper (file1.v)
Constant Module Prefix - Helper
Variables and constants store state in V programs. This lesson on File1 covers declaration rules, default values, scopes, or constant naming conventions.
Additional Context from Repository docs:
This example demonstrates the concepts of file1.
module mod1
const greet_count = 5
pub fn do_work() {
println(greet_count)
}
Variables
Parallel Declaration Immutable Variables
Parallel Declaration Immutable Variables
In V, you can declare and initialize multiple variables in a single line. This is known as parallel declaration. By default, variables in V are immutable (read-only). Once assigned a value, they cannot be changed.
This program demonstrates declaring two variables a and b at the same time and assigning them initial values. Any attempt to modify a or b later in the code will cause a compile-time error.
Additional Context from Repository docs:
This example demonstrates the concepts of parallel declaration immutable variables.
fn main() {
first_name, last_name, age := 'Ada', 'Lovelace', 36
println('${first_name} ${last_name} is ${age} years old')
println('Next milestone: ${first_name} will speak at the conference')
}
Parallel Declaration Mutable Variables
Parallel Declaration Mutable Variables
If you want to modify parallelly declared variables later, you must explicitly mark them as mutable using the mut keyword. In V, mutability is always explicit to make code safer and easier to reason about.
Here, we declare two mutable variables a and b at the same time using mut. We then reassign their values using the standard assignment operator (=).
Additional Context from Repository docs:
This example demonstrates the concepts of parallel declaration mutable variables.
fn main() {
mut greeting, mut recipient := 'Hi', 'world'
println('${greeting}, ${recipient}!')
greeting, recipient = 'Hello', 'Ada'
println('${greeting}, ${recipient}!')
}
Parallel Declaration Mut And Immutable Vars
Parallel Declaration Mut And Immutable Vars
Variables and constants store state in V programs. This lesson on Parallel Declaration Mut And Immutable Vars covers declaration rules, default values, scopes, or constant naming conventions.
Additional Context from Repository docs:
This example demonstrates the concepts of parallel declaration mut and immutable vars.
fn main() {
mut message, count := 'Hello', 32
println(message)
message = 'Hi'
println(message)
println(count)
}
Augmented Assignment String
Augmented Assignment String
Variables and constants store state in V programs. This lesson on Augmented Assignment String covers declaration rules, default values, scopes, or constant naming conventions.
Additional Context from Repository docs:
This example demonstrates the concepts of augmented assignment string.
fn main() {
mut greeting := 'Hi'
println(greeting)
greeting = greeting + ' there'
println(greeting)
greeting += ', how are you today?'
println(greeting)
}
Augmented Assignment Integer
Augmented Assignment Integer
Variables and constants store state in V programs. This lesson on Augmented Assignment Integer covers declaration rules, default values, scopes, or constant naming conventions.
Additional Context from Repository docs:
This example demonstrates the concepts of augmented assignment integer.
fn main() {
mut score := 10
println(score)
score = score + 5
println(score)
score += 5
println(score)
}
Declare Mutable Variable
Declare Mutable Variable
By default, all variables in V are immutable (their values cannot change). To declare a variable whose value can be modified later, you must prepend the mut keyword before the variable name.
This example shows how to declare a mutable variable, change its value, and print the results.
Additional Context from Repository docs:
This example demonstrates the concepts of declare mutable variable.
fn main() {
mut counter := 0
counter += 1
println(counter)
}
Cannot Update Mutable With Another Type
Cannot Update Mutable With Another Type
Variables and constants store state in V programs. This lesson on Cannot Update Mutable With Another Type covers declaration rules, default values, scopes, or constant naming conventions.
Additional Context from Repository docs:
This example demonstrates the concepts of cannot update mutable with another type.
fn main() {
mut i := 10
i = 100
i = 'Apple' // throws error
}
Declare Immutable Variable
Declare Immutable Variable
In V, variables are immutable by default. This design choice prevents accidental state mutation bugs, making code easier to reason about and safer for concurrency. When you declare a variable using the declaration operator :=, you are creating a read-only variable. If you try to reassign this variable later, the compilation will fail. This approach is similar to declaring constants in other languages, but it operates at the local scope level.
This example demonstrates how to declare an immutable variable and print its value.
Additional Context from Repository docs:
This example demonstrates the concepts of declare immutable variable.
fn main() {
// 'msg' is initialized as an immutable string variable using :=
msg := 'Hello'
println(msg)
}
Cannot Update Immutable Variables
Cannot Update Immutable Variables
One of V's core safety features is immutability by default. If you declare a variable without the mut keyword and then try to reassign it a new value, the compiler will refuse to compile the program.
This example demonstrates what happens when you try to update an immutable variable (expect a compiler error).
Additional Context from Repository docs:
This example demonstrates the concepts of cannot update immutable variables.
fn main() {
msg := 'Hello'
msg = 'Good Day!' // throws error
}
Declared And Assigned
Declared And Assigned
Variables and constants store state in V programs. This lesson on Declared And Assigned covers declaration rules, default values, scopes, or constant naming conventions.
Additional Context from Repository docs:
This example demonstrates the concepts of declared and assigned.
fn main() {
mut i := 0
// declared and assigned
println(i)
}
Declared And Not Assigned
Declared And Not Assigned
V does not allow variables to be declared without an initial value. Unlike other languages that initialize variables to a default 'zero' value or null, V forces you to explicitly provide a value. This prevents uninitialized variable bugs.
This example illustrates that declaring a variable without an assignment is a compilation error.
Additional Context from Repository docs:
This example demonstrates the concepts of declared and not assigned.
fn main() {
mut a // throws error
}
Unused Variables Will Be Warned
Unused Variables Will Be Warned
To keep codebases clean and efficient, the V compiler detects if you declare a variable but never use (consume) it. By default, V treats unused variables as a compilation warning/error, encouraging you to clean up dead code.
This example shows a declared variable that is never used.
Additional Context from Repository docs:
This example demonstrates the concepts of unused variables will be warned.
fn main() {
i := 'hello' // i is not used anywhere, so warns when run in dev mode and throws error when run in prod mode
x := 3
y := 2
println(x + y)
}
Global Variables Not Allowed - Scope Demo
Global Variables Not Allowed - Scope Demo
V does not allow global variables by default. Global state is a major source of bugs, race conditions in multi-threaded applications, and poor code structure. By forbidding globals, V enforces clean, modular code passing state via arguments.
These examples demonstrate that declaring variables outside of the main function or modules is strictly prohibited.
Additional Context from Repository docs:
This example demonstrates the concepts of global variables not allowed.
module main
fn method1() {
msg := 'Hello from Method1'
println(msg)
}
fn main() {
method1()
println(msg) // Will throw error as msg declared and accessible only in method1
}
Global Variables Not Allowed - File Scope Demo
Global Variables Not Allowed - File Scope Demo
V does not allow global variables by default. Global state is a major source of bugs, race conditions in multi-threaded applications, and poor code structure. By forbidding globals, V enforces clean, modular code passing state via arguments.
These examples demonstrate that declaring variables outside of the main function or modules is strictly prohibited.
Additional Context from Repository docs:
This example demonstrates the concepts of global variables not allowed.
module main
fn method1() {
if true {
mut b := 10
b++
}
println(b)
}
fn main() {
method1()
}
Variable Redeclaration
Variable Redeclaration
Variables and constants store state in V programs. This lesson on Variable Redeclaration covers declaration rules, default values, scopes, or constant naming conventions.
Additional Context from Repository docs:
This example demonstrates the concepts of variable redeclaration.
module main
fn main() {
x := 3
y := 2
println(x + y)
x := 5 // re-definition of variable x is not allowed
}
Variable Scope For Same Variable Names
Variable Scope For Same Variable Names
In V, variables are strictly scoped to the function or block in which they are declared. This lexical scoping means that two different functions can declare variables with the exact same name (e.g., msg) without any collision or interference. The compiler guarantees that these variables occupy separate locations in memory and are completely isolated from one another. This allows developers to use common, context-appropriate names like temp, id, or msg locally inside individual functions without worrying about global or cross-functional namespace pollution.
This program illustrates how msg is declared separately in both method1 and method2, showing scope isolation in action.
Additional Context from Repository docs:
This example demonstrates the concepts of variable scope for same variable names.
module main
fn method1() {
// 'msg' is local only to method1
msg := 'Hello from Method1'
println(msg)
}
fn method2() {
// 'msg' is local only to method2; does not conflict with method1's 'msg'
msg := 'Hello from Method2'
println(msg)
}
fn main() {
method1()
method2()
}
Variable Shadowing Not Allowed
Variable Shadowing Not Allowed
Variable shadowing happens when a variable declared within an inner scope (like an if block, a loop, or a function body) has the same name as a variable in an outer scope. V strictly forbids variable shadowing at the compiler level. Prohibiting shadowing prevents a class of common bugs where a developer accidentally updates a local inner variable instead of the intended outer variable, or vice versa.
This example demonstrates how V rejects shadowed variable declarations.
Additional Context from Repository docs:
This example demonstrates the concepts of variable shadowing not allowed.
module main
fn scope_demo() {
// 'x' is declared in the function's main scope
x := 10
println(x)
if true {
// ERROR: Declaring another variable named 'x' in an inner block is forbidden.
// To fix this, you must name the inner variable something else.
x := 20
println(x)
}
println(x)
}
fn main() {
scope_demo()
}
Chapter 3 Primitive Data Types
Quick Access
Below is an index of all code examples in this chapter. You can use these links to jump directly to any specific code example:
Primitive Types Demo
Boolean Type
Numeric Types
- Declaring Integers
- Hex Binary Octa Notation Of Declaring Integers
- Promoting Numeric Types
- Arithmetic Operators
- Bitwise Operators
- Shift Operators
- Shift Operator On Range Of Integers
- Integer Methods
- Float Methods
- U8 Methods
- Size Pointer Methods
Rune Type
String Type
- Declare String
- String Read Only Array Of Bytes
- Strings Immutable By Default
- Declaring Mutable Strings
- Cannot Mutate String Elements
- String Interpolation
- Escape Special Characters
- Declare Raw Strings
- String Concatenation Using Plus Sign
- String Concatenation Using Interpolation
- Extract Substring From String Literal
- Split String
- String To Runes Array
- Count Sub String Occurences
- Check String Contains Substring
- String Contains Is Case Sensitive
- Common String Methods
Primitive Types Demo
Primitive Types Demo Code
Primitive Types Demo Code
This comprehensive example demonstrates every primitive data type in V:
- Boolean:
bool(representingtrueorfalse). - String:
string(representing an immutable array of bytes). - Rune:
rune(representing a single Unicode code point, alias foru32). - Signed Integers:
i8(8-bit),i16(16-bit),int(32-bit),i64(64-bit). - Unsigned Integers:
u8(8-bit, aliasbyte),u16(16-bit),u32(32-bit),u64(64-bit). - Platform-dependent sizes:
isize(signed size of a pointer),usize(unsigned size of a pointer). - Floating Point Numbers:
f32(32-bit single-precision),f64(64-bit double-precision).
For each type, the example initializes a value and prints its value, type (using typeof(var).name), and size in bytes (using sizeof(var)).
Additional Context from Repository docs:
This example demonstrates the concepts of primitive types demo.
module main
fn main() {
println('==================================================')
println(' Vlang Primitive Data Types Demo ')
println('==================================================')
// 1. Boolean Type
b := true
println('Boolean: val: ${b} | type: ${typeof(b).name} | size: ${sizeof(b)} byte')
// 2. String Type
s := 'Hello, V!'
println('String: val: "${s}" | type: ${typeof(s).name} | size: ${sizeof(s)} bytes')
// 3. Rune Type (unicode character, represented as `r` prefix or backticks)
r := `V`
println('Rune: val: ${r} (char: ${r.str()}) | type: ${typeof(r).name} | size: ${sizeof(r)} bytes')
// 4. Signed Integers
i_8 := i8(-128)
i_16 := i16(-32768)
i_32 := int(-2147483648)
i_64 := i64(-9223372036854775808)
println('i8: val: ${i_8} | type: ${typeof(i_8).name} | size: ${sizeof(i_8)} byte')
println('i16: val: ${i_16} | type: ${typeof(i_16).name} | size: ${sizeof(i_16)} bytes')
println('int: val: ${i_32} | type: ${typeof(i_32).name} | size: ${sizeof(i_32)} bytes')
println('i64: val: ${i_64} | type: ${typeof(i_64).name} | size: ${sizeof(i_64)} bytes')
// 5. Unsigned Integers
u_8 := u8(255)
u_16 := u16(65535)
u_32 := u32(4294967295)
u_64 := u64(18446744073709551615)
println('u8: val: ${u_8} | type: ${typeof(u_8).name} | size: ${sizeof(u_8)} byte')
println('u16: val: ${u_16} | type: ${typeof(u_16).name} | size: ${sizeof(u_16)} bytes')
println('u32: val: ${u_32} | type: ${typeof(u_32).name} | size: ${sizeof(u_32)} bytes')
println('u64: val: ${u_64} | type: ${typeof(u_64).name} | size: ${sizeof(u_64)} bytes')
// 6. Platform-dependent Sizes
isize_val := isize(-12345)
usize_val := usize(12345)
println('isize: val: ${isize_val} | type: ${typeof(isize_val).name} | size: ${sizeof(isize_val)} bytes')
println('usize: val: ${usize_val} | type: ${typeof(usize_val).name} | size: ${sizeof(usize_val)} bytes')
// 7. Floating Point Numbers
f_32 := f32(3.14159)
f_64 := f64(2.718281828459)
println('f32: val: ${f_32} | type: ${typeof(f_32).name} | size: ${sizeof(f_32)} bytes')
println('f64: val: ${f_64} | type: ${typeof(f_64).name} | size: ${sizeof(f_64)} bytes')
println('==================================================')
}
V is a statically-typed language, meaning every variable has a fixed data type at compile time. In this chapter, you will learn about V's primitive types: booleans for logic, numeric types for numbers, runes for single characters, and strings for text. You will also learn about V's rich set of built-in methods on these types.
Boolean Type
Logical Operators
Logical Operators
In V, primitive data types are the core building blocks of the language. This section details how to declare and use Logical Operators in a simple, straightforward manner. Beginners should pay close attention to how variables of this type are initialized and how built-in methods are called on them.
Additional Context from Repository docs:
This example demonstrates the concepts of logical operators.
module main
fn main() {
t := true
f := false
// Logical And using && operator
and_tt := t && t
and_tf := t && f
and_ft := f && t
and_ff := f && f
println('Logical And using && operator')
println('${t} && ${t} = ${and_tt}')
println('${t} && ${f} = ${and_tf}')
println('${f} && ${t} = ${and_ft}')
println('${f} && ${f} = ${and_ff}')
println('')
// Logical OR using || operator
or_tt := t || t
or_tf := t || f
or_ft := f || t
or_ff := f || f
println('Logical OR using || Operator')
println('${t} || ${t} = ${or_tt}')
println('${t} || ${f} = ${or_tf}')
println('${f} || ${t} = ${or_ft}')
println('${f} || ${f} = ${or_ff}')
println('')
// Logical not using ! Operator
not_t := !t
not_f := !f
println('Logical not using ! Operator')
println('!${t} = ${not_t}')
println('!${f} = ${not_f}')
}
Relational Operators
Relational Operators
In V, primitive data types are the core building blocks of the language. This section details how to declare and use Relational Operators in a simple, straightforward manner. Beginners should pay close attention to how variables of this type are initialized and how built-in methods are called on them.
Additional Context from Repository docs:
This example demonstrates the concepts of relational operators.
module main
struct Note {
id int
detail string
completed bool
}
fn main() {
mut n := Note{
id: 1001
detail: 'get groceries'
}
println(n.completed) // un-assigned bool field will be false by default
// Comparing using Relational operator >
if n.id > 1000 { // comparison of note id of integer type to another integer evaluates to a boolean
println('The note id is greater than 1000')
} else {
println('The note id is less than 1000')
}
// Comparing using Relational operator ==
if n.detail == 'get groceries' {
println('The note details about groceries')
}
// Comparing using Relational operator !=
if n.detail != 'get dairy products' {
println('The note does not details about dairy products')
}
}
Boolean Methods
Boolean Methods
Booleans in V are simple true or false values. V provides built-in methods on boolean types, such as str(), which converts the boolean value to its string representation ('true' or 'false').
This is useful for logging, printing, or interpolating booleans into strings.
Additional Context from Repository docs:
This example demonstrates the concepts of boolean methods.
module main
fn main() {
t := true
f := false
// str() returns string representation ('true' or 'false')
println(t.str()) // true
println(f.str()) // false
}
Numeric Types
Declaring Integers
Declaring Integers
V has several built-in integer types, both signed and unsigned, of various sizes (e.g., i8, i16, i32, i64 for signed integers, and u8, u16, u32, u64 for unsigned integers). If you declare an integer using :=, V defaults to the standard 32-bit integer (int).
This example demonstrates how to declare different integer types.
Additional Context from Repository docs:
This example demonstrates the concepts of declaring integers.
module main
fn main() {
x := 1
println(typeof(x).name)
// int
i := 1_000
j := 1000
println(i == j) // true
}
Hex Binary Octa Notation Of Declaring Integers
Hex Binary Octa Notation Of Declaring Integers
V has several built-in integer types, both signed and unsigned, of various sizes (e.g., i8, i16, i32, i64 for signed integers, and u8, u16, u32, u64 for unsigned integers). If you declare an integer using :=, V defaults to the standard 32-bit integer (int).
This example demonstrates how to declare different integer types.
Additional Context from Repository docs:
This example demonstrates the concepts of hex binary octa notation of declaring integers.
module main
fn demo() {
h1 := 0x64 // hexadecimal starts with 0x
b1 := 0b1100100 // binary starts with 0b
o1 := 0o144 // Octal starts with 0o
println('Value of var h1 with hexadecimal value : ${h1}')
println('Data type of var h1 with hexadecimal value : ${typeof(h1).name}')
println('Value of var b1 with binary value : ${b1}')
println('Data type of var b1 with binary value : ${typeof(b1).name}')
println('Value of var o1 with octal value : ${o1}')
println('Data type of var o1 with octal value : ${typeof(o1).name}')
}
fn main() {
demo()
}
Promoting Numeric Types
Promoting Numeric Types
V is very strict about types. It does not perform implicit type conversion (coercion) between different numeric types to prevent accidental precision loss or overflow bugs. If you want to perform arithmetic operations on different types, you must explicitly cast them.
This example shows how to cast (promote) smaller integer types to larger ones or to floats.
Additional Context from Repository docs:
This example demonstrates the concepts of promoting numeric types.
module main
fn demo() {
ia := i8(2)
ib := i16(2)
ic := int(2)
println('----type definitions----')
println('variable ia is of type: ${typeof(ia).name}')
println('variable ib is of type: ${typeof(ib).name}')
println('variable ic is of type: ${typeof(ic).name}')
println('')
iaa := ia + ia // i8 with i8 results i8
ibb := ib + ib // i16 with i16 results i16
icc := ic + ic // int with int results int
println('----mixing types----')
println('variable iaa is of type: ${typeof(iaa).name}, after adding type ${typeof(ia).name} with itself')
println('variable ibb is of type: ${typeof(ibb).name}, after adding type ${typeof(ib).name} with itself')
println('variable icc is of type: ${typeof(icc).name}, after adding type ${typeof(ic).name} with itself')
println('')
iab := ia + ib // i8 with i16 results in i16
ibc := ib - ic // i16 with i32 results in i32
println('----type promotion----')
println('variable iab is promoted to type: ${typeof(iab).name}, after adding type ${typeof(ia).name} with ${typeof(ib).name}')
println('variable ibc is promoted to type: ${typeof(ibc).name}, after subtracting type ${typeof(ib).name} with ${typeof(ic).name}')
iba := ib / ia // the division of i16 and i8 types
println('Variable iba is promoted to the higher data type ${typeof(iba).name} which is carried from ib of type ${typeof(ib).name} divided from variable ia of type ${typeof(ia).name}')
fa := f32(2)
fa_iba := fa + iba // fa is type of f32 and iba is of type i32
println('Variable fa_iba is promoted to the higher data type ${typeof(fa_iba).name} which is carried from fa of type ${typeof(fa).name} when added with variable iba of type ${typeof(iba).name}')
}
fn main() {
demo()
}
Arithmetic Operators
Arithmetic Operators
In V, primitive data types are the core building blocks of the language. This section details how to declare and use Arithmetic Operators in a simple, straightforward manner. Beginners should pay close attention to how variables of this type are initialized and how built-in methods are called on them.
Additional Context from Repository docs:
This example demonstrates the concepts of arithmetic operators.
module main
fn main() {
a := 10
b := 2
// add using +
sum := a + b
// subtract using -
diff := b - a
// product using *
prod := a * b
// / results in quotient
quotient := a / b
// % modulo results in remainder
remainder := a % b
println('Sum of ${a} and ${b} is ${sum}')
println('Subtracting ${a} from ${b} is ${diff}')
println('Product of ${a} and ${b} is ${prod}')
println('Quotient when ${a} divided by ${b} is ${quotient}')
println('Remainder when ${a} divided by ${b} is ${remainder}')
}
Bitwise Operators
Bitwise Operators
In V, primitive data types are the core building blocks of the language. This section details how to declare and use Bitwise Operators in a simple, straightforward manner. Beginners should pay close attention to how variables of this type are initialized and how built-in methods are called on them.
Additional Context from Repository docs:
This example demonstrates the concepts of bitwise operators.
module main
fn main() {
a := 0b00000110 // 6
b := 0b00000010 // 2
// bitwise AND operation of two binary nums using & operator
b_and := a & b
// bitwise OR operation of two binary nums using | operator
b_or := a | b
// bitwise XOR operation of two binary nums using ^ operator
b_xor := a ^ b
// bitwise NOT operation of an binary nums using ~ operator
not_a := ~a // Not operation yields value which is equal to -(a+1) in its integer form
println('Bitwise AND: ${a:08b} & ${b:08b} = ${b_and:08b}')
println('Bitwise OR: ${a:08b} | ${b:08b} = ${b_or:08b}')
println('Bitwise XOR: ${a:08b} ^ ${b:08b} = ${b_xor:08b}')
println('Bitwise NOT: ~${a:b} = ${not_a:b}')
}
Shift Operators
Shift Operators
In V, primitive data types are the core building blocks of the language. This section details how to declare and use Shift Operators in a simple, straightforward manner. Beginners should pay close attention to how variables of this type are initialized and how built-in methods are called on them.
Additional Context from Repository docs:
This example demonstrates the concepts of shift operators.
module main
fn main() {
// declare 8 bit integer with value 3
a := i8(3)
// 8 bits equals to 1 byte
println('a is ${sizeof(a)} byte(s)') // a is 1 byte(s)
// declare 8-bit unsigned integer to shift by 1 position
pos := byte(1)
// Shift left the value 3 by 1 position
a_left_shift := a << pos
println('${a} << ${pos} = ${a_left_shift}')
}
Shift Operator On Range Of Integers
Shift Operator On Range Of Integers
In V, primitive data types are the core building blocks of the language. This section details how to declare and use Shift Operator On Range Of Integers in a simple, straightforward manner. Beginners should pay close attention to how variables of this type are initialized and how built-in methods are called on them.
Additional Context from Repository docs:
This example demonstrates the concepts of shift operator on range of integers.
module main
fn main() {
val := i8(1)
bits := sizeof(val) * 8
println('Performing left shift using << Operator')
for i in 0 .. bits {
after_shift := val << i
println('$val << $i = $after_shift \/\/ type after shift operation: ${typeof(after_shift).name}')
}
}
Integer Methods
Integer Methods
In V, primitive data types are the core building blocks of the language. This section details how to declare and use Integer Methods in a simple, straightforward manner. Beginners should pay close attention to how variables of this type are initialized and how built-in methods are called on them.
Additional Context from Repository docs:
This example demonstrates the concepts of integer methods.
module main
fn main() {
x := 42
// str() returns string representation of the integer
println(x.str()) // "42"
// hex() returns hexadecimal representation without prefix
println(x.hex()) // "2a"
// hex2() returns hexadecimal representation with "0x" prefix
println(x.hex2()) // "0x2a"
// hex_full() returns hexadecimal representation with full width padding for the type (8 digits for 32-bit int)
println(x.hex_full()) // "0000002a"
}
Float Methods
Float Methods
In V, primitive data types are the core building blocks of the language. This section details how to declare and use Float Methods in a simple, straightforward manner. Beginners should pay close attention to how variables of this type are initialized and how built-in methods are called on them.
Additional Context from Repository docs:
This example demonstrates the concepts of float methods.
module main
fn main() {
f := 12345.6789
// str() returns string representation of the float
println(f.str()) // "12345.6789"
// strg() returns string representation (often identical to str())
println(f.strg()) // "12345.6789"
// strlong() returns a full/long string representation of the float
println(f.strlong()) // "12345.6789"
// strsci(precision) returns scientific notation with specified precision/decimal places
println(f.strsci(4)) // "1.2346e+04"
// eq_epsilon(other) performs a comparison using machine epsilon (for near-equality)
f2 := 12345.678900000001
println(f.eq_epsilon(f2)) // true
}
U8 Methods
U8 Methods
In V, primitive data types are the core building blocks of the language. This section details how to declare and use U8 Methods in a simple, straightforward manner. Beginners should pay close attention to how variables of this type are initialized and how built-in methods are called on them.
Additional Context from Repository docs:
This example demonstrates the concepts of u8 methods.
module main
fn main() {
b := u8(65) // ASCII code for 'A'
// str() returns string representation of the numeric value
println(b.str()) // "65"
// ascii_str() returns string of length 1 containing the character
println(b.ascii_str()) // "A"
// hex() returns hexadecimal representation
println(b.hex()) // "41"
// hex_full() returns hexadecimal representation (same as hex() for u8)
println(b.hex_full()) // "41"
// is_alnum() checks if the character is alphanumeric
println(b.is_alnum()) // true
// is_bin_digit() checks if the character is a binary digit ('0' or '1')
println(b.is_bin_digit()) // false
// is_capital() checks if the character is an uppercase letter
println(b.is_capital()) // true
// is_digit() checks if the character is a decimal digit ('0'-'9')
println(b.is_digit()) // false
// is_hex_digit() checks if the character is a hexadecimal digit ('0'-'9', 'a'-'f', 'A'-'F')
println(b.is_hex_digit()) // true
// is_letter() checks if the character is an alphabetic letter
println(b.is_letter()) // true
// is_oct_digit() checks if the character is an octal digit ('0'-'7')
println(b.is_oct_digit()) // false
// is_space() checks if the character is a whitespace character
println(b.is_space()) // false
// repeat(count) repeats the character count times and returns a string
println(b.repeat(3)) // "AAA"
// str_escaped() returns an escaped string representation of the character
println(b.str_escaped()) // "A"
}
Size Pointer Methods
Size Pointer Methods
In V, primitive data types are the core building blocks of the language. This section details how to declare and use Size Pointer Methods in a simple, straightforward manner. Beginners should pay close attention to how variables of this type are initialized and how built-in methods are called on them.
Additional Context from Repository docs:
This example demonstrates the concepts of size and pointer methods.
module main
fn main() {
// isize and usize methods
sz := isize(100)
usz := usize(200)
// str() returns string representation
println(sz.str()) // "100"
println(usz.str()) // "200"
// voidptr methods
x := 42
p := voidptr(&x)
// str() returns the memory address as string
println(p.str().starts_with('0x')) // true
// hex_full() returns full-width hex representation of address
println(p.hex_full().len > 0) // true
// vbytes(len) returns a byte array representation of the memory pointed to (must be called in unsafe block)
unsafe {
bytes := p.vbytes(int(sizeof(int)))
println(bytes) // [42, 0, 0, 0]
}
}
Rune Type
Declare Rune
Declare Rune
A rune in V represents a single Unicode code point. Runes are declared using backticks (e.g., \a\, \🔥\) and are represented internally as 32-bit unsigned integers (u32). This allows V to support multi-byte Unicode characters (like emojis or Chinese characters) as single character tokens.
This example shows how to declare and print runes.
Additional Context from Repository docs:
This example demonstrates the concepts of declare rune.
fn main() {
// A rune stores a single Unicode character.
l := `a`
println(typeof(l).name)
// rune
}
Rune Operations With Strings
Rune Operations With Strings
Since strings in V are arrays of UTF-8 encoded bytes, you cannot directly check for a rune inside a string using string operations unless the rune is first converted to a string. V provides .str() on the rune type to easily convert a rune to a 1-character string, allowing you to use string methods like .count(), .contains(), etc.
This example shows how to count occurrences of a Unicode rune in a string.
Additional Context from Repository docs:
This example demonstrates the concepts of rune operations with strings.
fn main() {
beverage := 'café'
// 's' is a rune representing the Unicode character 'é'
s := `é`
// Since .count() expects a string argument, we convert the rune 's' using .str()
println(beverage.count(s.str()))
// Outputs: 1
}
Rune Methods
Rune Methods
Runes in V are not just raw numbers; they are full-fledged Unicode characters that support several built-in methods. You can convert their case, check their byte length (which can range from 1 to 4 bytes depending on the character, such as emojis), obtain their byte array representation, or convert them to hexadecimal code points.
This lesson demonstrates common helper methods on the rune type.
Additional Context from Repository docs:
This example demonstrates the concepts of rune methods.
module main
fn main() {
r := `A`
// bytes() returns the byte representation (UTF-8 bytes) of the rune
println(r.bytes()) // [65]
// hex() returns the hexadecimal representation of the rune code point
println(r.hex()) // "41"
// length_in_bytes() returns the size of the rune in bytes (1 to 4)
println(r.length_in_bytes()) // 1
// repeat(count) returns a string with the rune repeated count times
println(r.repeat(3)) // "AAA"
// str() returns the string representation of the rune
println(r.str()) // "A"
// to_lower() returns the lowercase rune
println(r.to_lower().str()) // "a"
// to_upper() returns the uppercase rune
println(r.to_upper().str()) // "A"
// to_title() returns the titlecase rune
println(r.to_title().str()) // "A"
// Testing with a multi-byte UTF-8 rune (dog emoji 🐕)
r2 := `🐕`
println(r2.bytes()) // [240, 159, 144, 149]
println(r2.hex()) // "1f415" (Unicode code point in hex)
println(r2.length_in_bytes()) // 4
println(r2.repeat(2)) // "🐕🐕"
println(r2.str()) // "🐕"
}
String Type
Declare String
Declare String
In V, strings are representing read-only arrays of bytes, encoded natively in UTF-8. You can declare string variables using single quotes ('hello') or double quotes ("hello"), though single quotes are preferred in idiomatic V. The string type in V comes with built-in metadata, such as the .len field, which returns the total number of bytes in the string (not necessarily the number of Unicode characters/runes). You can inspect the runtime type of any variable using V's built-in typeof() function.
This example illustrates how to declare string variables, concatenate them, check string lengths, and query variable types at runtime.
Additional Context from Repository docs:
This example demonstrates the concepts of declare string.
module main
fn main() {
// Strings can be declared using single quotes
greeting := 'hello'
name := 'Ada'
// String concatenation using the '+' operator
message := greeting + ', ' + name + '!'
println(message)
// Access the length (in bytes) of the string using the .len field
println('Length: ${message.len}')
// Inspect the variable type at runtime using typeof()
println('Type: ${typeof(message).name}')
}
String Read Only Array Of Bytes
String Read Only Array Of Bytes
In V, strings are represented internally as read-only arrays of UTF-8 encoded bytes (u8). This means you can index into a string using bracket notation (str[index]) to extract the raw byte value at that position. The type returned from indexing a string is always byte (an alias for u8 in V), not a string or rune.
This example shows how to read raw bytes from a string and prints the byte value and its type name.
Additional Context from Repository docs:
This example demonstrates the concepts of string read only array of bytes.
fn main() {
fruit := 'Orange'
// Accessing the first byte of the string 'Orange'.
// This returns the ASCII value of 'O' (which is 79), and its type is 'byte'.
println(typeof(fruit[0]).name)
// Outputs: byte
println(fruit[0])
// Outputs: 79
}
Strings Immutable By Default
Strings Immutable By Default
Strings in V are completely immutable. Once a string is created, its characters cannot be modified in place. Any operation that manipulates a string (such as replacing characters or converting to uppercase) returns a brand new string instead of modifying the original.
This example demonstrates that strings are read-only and cannot be changed.
Additional Context from Repository docs:
This example demonstrates the concepts of strings immutable by default.
fn main() {
s := 'hello'
// variable s is immutable
s = 'Hello!'
// this results in error
}
Declaring Mutable Strings
Declaring Mutable Strings
While strings are immutable, you can declare a mutable string variable using mut. This allows the variable to be reassigned to a new string value, or appended to using the += operator.
This example shows how to declare a mutable string and append text to it.
Additional Context from Repository docs:
This example demonstrates the concepts of declaring mutable strings.
fn main() {
mut msg := 'Hello Friend!'
msg = 'Hope you are doing good.'
println(msg)
// Hope you are doing good.
msg = msg + ' There is a surprise for you.'
println(msg)
// Hope you are doing good. There is a surprise for you.
}
Cannot Mutate String Elements
Cannot Mutate String Elements
Although declaring a string variable with mut lets you reassign the variable to reference a completely different string, it does NOT let you mutate individual characters or bytes within the existing string (e.g. s[0] = \G\``). String data elements are strictly read-only arrays of bytes, and attempting to mutate them will fail compilation.
This program shows that element mutation is strictly forbidden.
Additional Context from Repository docs:
This example demonstrates the concepts of cannot mutate string elements.
fn main() {
mut greet := 'good Day'
// ERROR: Cannot assign directly to string indices.
// Strings in V are read-only byte arrays and their contents cannot be mutated.
greet[0] = 'G'
}
String Interpolation
String Interpolation
String interpolation is a clean way to insert variables or expressions inside a string literal. In V, you do this by wrapping the variable or expression in ${variable} inside a single-quoted string.
This example demonstrates how to format strings with variable values.
Additional Context from Repository docs:
This example demonstrates the concepts of string interpolation.
fn main() {
a := 'coding'
b := 'fun'
println('${a} is ${b}')
println('${a} is ${b}')
}
Escape Special Characters
Escape Special Characters
V strings support standard escape characters (like \n for newlines, \t for tabs, and \\ for backslashes) to represent special characters inside a string literal.
This example shows how these escape sequences are rendered in the console.
Additional Context from Repository docs:
This example demonstrates the concepts of escape special characters.
module main
fn main() {
// 1. Newline escape character (\n)
println('Hello\nWorld!')
// 2. Tab escape character (\t)
println('Name:\tAlice\tAge:\t25')
// 3. Backslash escape character (\\)
println('File path: C:\\Program Files\\V')
// 4. Escaping single quotes (\') in a single-quoted string
println("It's my Daughter's birthday!")
// 5. Escaping double quotes (\") in a double-quoted string
println('She said, "V is fast!"')
}
Declare Raw Strings
Declare Raw Strings
If you want to write a string literal where escape sequences (like \n) are treated as literal text instead of special commands, you can declare a raw string by prefixing the string literal with r (e.g., r\'hello\nworld\').
This is extremely useful when writing regular expressions or file paths.
Additional Context from Repository docs:
This example demonstrates the concepts of declare raw strings.
module main
fn main() {
i := r'hi \how are you/?'
println(i)
}
String Concatenation Using Plus Sign
String Concatenation Using Plus Sign
In V, joining strings together is performed using the + operator. Since strings are immutable byte arrays, each concatenation creates a brand-new string in memory and copies the contents of both source strings. While the + operator is extremely convenient and clear for joining a few strings, doing this in loops or performance-critical code paths is discouraged because it leads to excessive memory allocations. For high-performance string building, V offers the strings.Builder module.
This example illustrates the direct concatenation of two string variables.
Additional Context from Repository docs:
This example demonstrates the concepts of string concatenation using plus sign.
module main
fn main() {
a := 'con'
b := 'cat'
// Concatenate a and b, creating a new string 'concat' in memory
println(a + b)
// concat
}
String Concatenation Using Interpolation
String Concatenation Using Interpolation
In V, primitive data types are the core building blocks of the language. This section details how to declare and use String Concatenation Using Interpolation in a simple, straightforward manner. Beginners should pay close attention to how variables of this type are initialized and how built-in methods are called on them.
Additional Context from Repository docs:
This example demonstrates the concepts of string concatenation using interpolation.
module main
fn main() {
i := 1
j := 'man army'
println('${i} ${j}')
}
Extract Substring From String Literal
Extract Substring From String Literal
V provides two main techniques to extract substrings from a string literal or variable:
- The
.substr(start, end)Method: Takes the starting index (inclusive) and ending index (exclusive) as parameters. - Range Slicing Syntax
[start..end]: A clean and idiomatic syntax (similar to Go and Rust) where you specify range offsets. If the starting index is omitted (e.g.[..end]), it defaults to0. If the ending index is omitted (e.g.[start..]), it defaults to the length of the string.
Both techniques are demonstrated in the example below.
Additional Context from Repository docs:
This example demonstrates the concepts of extract substring from string literal.
module main
fn main() {
a := 'Camel'
// Method 1: Using the substr(start, end) method
// Extracts characters from index 0 up to (but not including) index 3
b := a.substr(0, 3)
println(b) // Output: Cam
// Method 2: Using idiomatic range slicing syntax [start..end] (similar to Go/Rust)
// Slices from index 1 up to (but not including) index 4
c := a[1..4]
println(c) // Output: ame
// Method 3: Slicing from start to index [..end]
// If the start index is omitted, it defaults to 0
d := a[..3]
println(d) // Output: Cam
// Method 4: Slicing from index to end [start..]
// If the end index is omitted, it defaults to the string length
e := a[2..]
println(e) // Output: mel
}
Split String
Split String
In V, primitive data types are the core building blocks of the language. This section details how to declare and use Split String in a simple, straightforward manner. Beginners should pay close attention to how variables of this type are initialized and how built-in methods are called on them.
Additional Context from Repository docs:
This example demonstrates the concepts of split string.
module main
fn main() {
sp := 'The tiny tiger tied the tie tighter to its tail'
res := sp.split(' ')
// split by space as delimiter
println(typeof(res).name)
// []string
println(res)
// ['The', 'tiny', 'tiger', 'tied', 'the', 'tie', 'tighter', 'to', 'its', 'tail']
}
String To Runes Array
String To Runes Array
In V, primitive data types are the core building blocks of the language. This section details how to declare and use String To Runes Array in a simple, straightforward manner. Beginners should pay close attention to how variables of this type are initialized and how built-in methods are called on them.
Additional Context from Repository docs:
This example demonstrates the concepts of string to runes array.
module main
fn main() {
doge_moon := '🐕+🚀=🌑'
doge_moon_runes := doge_moon.runes()
println(doge_moon_runes)
println(typeof(doge_moon_runes).name) // []rune
}
Count Sub String Occurences
Count Sub String Occurences
In V, primitive data types are the core building blocks of the language. This section details how to declare and use Count Sub String Occurences in a simple, straightforward manner. Beginners should pay close attention to how variables of this type are initialized and how built-in methods are called on them.
Additional Context from Repository docs:
This example demonstrates the concepts of count sub string occurences.
module main
fn main() {
sp := 'The tiny tiger tied the tie tighter to its tail'
println(sp.count('t'))
// 10
println(sp.count('T'))
// 1
println(sp.count('tie'))
// 2
println(sp.count('-'))
// 0
}
Check String Contains Substring
Check String Contains Substring
In V, primitive data types are the core building blocks of the language. This section details how to declare and use Check String Contains Substring in a simple, straightforward manner. Beginners should pay close attention to how variables of this type are initialized and how built-in methods are called on them.
Additional Context from Repository docs:
This example demonstrates the concepts of check string contains substring.
module main
fn main() {
hs := 'monday'
if hs.contains('mon') {
println('${hs} contains mon')
} else {
println('${hs} does not contains mon')
}
}
String Contains Is Case Sensitive
String Contains Is Case Sensitive
In V, primitive data types are the core building blocks of the language. This section details how to declare and use String Contains Is Case Sensitive in a simple, straightforward manner. Beginners should pay close attention to how variables of this type are initialized and how built-in methods are called on them.
Additional Context from Repository docs:
This example demonstrates the concepts of string contains is case sensitive.
module main
fn main() {
hs := 'Monday'
if hs.contains('mon') {
println('${hs} contains mon')
} else {
println('${hs} does not contains mon')
}
}
Common String Methods
Common String Methods
In V, primitive data types are the core building blocks of the language. This section details how to declare and use Common String Methods in a simple, straightforward manner. Beginners should pay close attention to how variables of this type are initialized and how built-in methods are called on them.
Additional Context from Repository docs:
This example demonstrates the concepts of common string methods.
module main
fn main() {
s := ' Hello, V! '
// to_lower() returns the lowercase version of the string
println(s.to_lower()) // " hello, v! "
// to_upper() returns the uppercase version of the string
println(s.to_upper()) // " HELLO, V! "
// trim_space() trims leading and trailing whitespaces
println(s.trim_space()) // "Hello, V!"
// trim(cutset) trims leading and trailing characters that match any character in the cutset
println(s.trim(' ')) // "Hello, V!"
// replace(old, new) replaces all occurrences of old with new
println(s.replace('V', 'World')) // " Hello, World! "
// replace_once(old, new) replaces the first occurrence of old with new
println(s.replace_once('l', 'x')) // " Hexlo, V! "
// index(sub) returns the start index of the first occurrence of sub as an optional ?int
idx := s.index('Hello') or { -1 }
println(idx) // 2
// last_index(sub) returns the start index of the last occurrence of sub as an optional ?int
last_idx := s.last_index('l') or { -1 }
println(last_idx) // 5
// starts_with(prefix) checks if the string starts with the prefix
println(s.starts_with(' ')) // true
// ends_with(suffix) checks if the string ends with the suffix
println(s.ends_with('!')) // false (ends with spaces)
// is_pure_ascii() checks if all characters in the string are pure ASCII
println(s.is_pure_ascii()) // true
// split_into_lines() splits a string into an array of lines
multiline := 'line 1\nline 2'
println(multiline.split_into_lines()) // ["line 1", "line 2"]
// split_by_space() splits a string by space as delimiter
println(s.split_by_space()) // ["Hello,", "V!"]
}
Chapter 4 Control Flow
Quick Access
Below is an index of all code examples in this chapter. You can use these links to jump directly to any specific code example:
Control Flow Extras
- Chaining Else If
- If With Goto
- Cascade Match Conditions
- Match As Switch Case
- Match Pattern Matching
- Match With Enum
- Match With Enum And Else
- Bare For
- Break For
- Continue For
- For C Style
- For On Array Without Index
- For On Arrays
- For On Maps
- For On Maps Ignore Key
- For On Range
- For With Continue Break And Labels
- Reverse For
Control flow determines the execution path of your code. In this chapter, we cover conditionals (if-else), pattern matching (match), and the versatile for loop. V simplifies control flow by using fewer keywords, making code highly readable.
Control Flow Extras
Chaining Else If
Chaining Else If
When your program must choose between multiple mutually exclusive paths, you can chain multiple else if blocks together. V evaluates these conditions sequentially from top to bottom. As soon as one condition evaluates to true, the corresponding block of code is executed, and all remaining branches (including any final fallback else block) are skipped entirely. Like single if statements, parentheses are not required around the condition expressions in V.
This example defines a helper function that takes a weekday string and prints a corresponding breakfast menu using a chained conditional statement.
Additional Context from Repository docs:
This example demonstrates the concepts of chaining else if.
module main
// This helper chooses a meal plan based on the weekday.
fn breakfast_menu(day string) {
// Cascading conditional checks
if day == 'Monday' {
println('Bread, Jam, Half boiled Egg')
} else if day == 'Tuesday' {
println('Bread, Jam, Juice')
} else if day == 'Wednesday' {
println('Milk, Bread, Fruit Bowl')
} else if day == 'Thursday' {
println('Bread, Jam, Juice')
} else if day == 'Friday' {
println('Cereals, Bread, Jam, Half boiled Egg')
} else if day == 'Saturday' {
println('Milk, Bread, Fruit Bowl')
} else if day == 'Sunday' {
println('Cereals, Bread, Jam, Half boiled Egg')
} else {
// Fallback block executed if no prior conditions match
println('invalid input')
}
}
fn main() {
// Call the helper with a sample weekday.
breakfast_menu('Saturday')
}
If With Goto
If With Goto
Control flow structures allow your program to decide which path of execution to take. This example demonstrates the usage of If With Goto in V, showing how to control execution paths cleanly and safely.
Additional Context from Repository docs:
This example demonstrates the concepts of if with goto.
module main
import os
fn main() {
improper_input_age:
println('Invalid input. Please provide value greater than 0.')
next_person:
inp := os.input('Enter your age:')
if inp != 'stop' {
age := inp.int()
if age >= 13 {
println('You are allowed to watch this movie')
} else if age > 0 && age < 13 {
println('Parental Guidance is required to watch this movie')
} else if age <= 0 {
unsafe {
goto improper_input_age
}
}
unsafe {
goto next_person
}
}
}
Cascade Match Conditions
Cascade Match Conditions
Control flow structures allow your program to decide which path of execution to take. This example demonstrates the usage of Cascade Match Conditions in V, showing how to control execution paths cleanly and safely.
Additional Context from Repository docs:
This example demonstrates the concepts of cascade match conditions.
module main
fn breakfast_menu(day string) string {
return match day {
'Monday' {
'Bread, Jam, Half boiled Egg'
}
'Tuesday', 'Thursday' {
'Bread, Jam, Juice'
}
'Wednesday' {
'Milk, Bread, Fruit Bowl'
}
'Friday', 'Sunday' {
'Cereals, Bread, Jam, Half boiled Egg'
}
'Saturday' {
'Milk, Bread, Fruit Bowl'
}
else {
'invalid input'
}
}
}
fn main() {
friday_menu := breakfast_menu('Friday')
println(friday_menu)
sunday_menu := breakfast_menu('Sunday')
println(sunday_menu)
tuesday_menu := breakfast_menu('Tuesday')
println(tuesday_menu)
thursday_menu := breakfast_menu('Thursday')
println(thursday_menu)
}
Match As Switch Case
Match As Switch Case
In V, there is no switch statement. Instead, the match keyword is used for branching on values. The match statement is highly readable and type-safe. Each branch is evaluated in order, and unlike in C/Java/Javascript, there is no "fall-through" behavior—the matching block executes and the statement completes immediately. This eliminates bugs caused by forgetting break statements. V also enforces that a match must cover all possible cases or provide an else block.
This example shows how to use match on string values.
Additional Context from Repository docs:
This example demonstrates the concepts of match as switch case.
module main
fn breakfast_menu(day string) {
// A match block branches on the value of 'day'.
// In V, match is an expression and can also return values.
match day {
'Monday' { println('Bread, Jam, Half boiled Egg') }
'Tuesday' { println('Bread, Jam, Juice') }
'Wednesday' { println('Milk, Bread, Fruit Bowl') }
'Thursday' { println('Bread, Jam, Juice') }
'Friday' { println('Cereals, Bread, Jam, Half boiled Egg') }
'Saturday' { println('Milk, Bread, Fruit Bowl') }
'Sunday' { println('Cereals, Bread, Jam, Half boiled Egg') }
else { println('invalid input') } // Exhaustive match requirement handled by 'else'
}
}
fn main() {
breakfast_menu('Sunday')
}
Match Pattern Matching
Match Pattern Matching
Control flow structures allow your program to decide which path of execution to take. This example demonstrates the usage of Match Pattern Matching in V, showing how to control execution paths cleanly and safely.
Additional Context from Repository docs:
This example demonstrates the concepts of match pattern matching.
module main
fn main() {
age := 18
res := match age {
0...18 { 'Person with age ${age} classified as a Child' }
19...120 { 'Person with age ${age} classified as an Adult' }
else { '${age} is must be in the range 0 to 120' }
}
println(res)
}
Match With Enum
Match With Enum
One of V's strongest safety guarantees is exhaustive enum matching. When you match on an enum value, V requires you to handle every single enum member. If you miss one, the compiler will refuse to compile the program. This ensures that when new items are added to an enum in the future, the compiler will automatically guide you to update all match expressions across your codebase. Additionally, V supports shorthand syntax where you can write .member_name instead of EnumName.member_name inside the match arms.
This example illustrates matching over an enum to return a string menu item.
Additional Context from Repository docs:
This example demonstrates the concepts of match with enum.
module main
// Define a Days-of-the-week enum
enum Day {
sunday
monday
tuesday
wednesday
thursday
friday
saturday
}
fn breakfast_menu(day Day) string {
// The match statement returns the value of the matching block.
// Notice we use the shorthand dot syntax (.monday) since 'day' is known to be of type 'Day'.
return match day {
.monday {
'Bread, Jam, Half boiled Egg'
}
.tuesday, .thursday {
'Bread, Jam, Juice' // Grouping multiple enum members using comma
}
.wednesday {
'Milk, Bread, Fruit Bowl'
}
.friday, .sunday {
'Cereals, Bread, Jam, Half boiled Egg'
}
.saturday {
'Milk, Bread, Fruit Bowl'
}
} // No else block is needed because all enum members are exhaustively handled.
}
fn main() {
friday_menu := breakfast_menu(Day.friday)
println(friday_menu)
sunday_menu := breakfast_menu(Day.sunday)
println(sunday_menu)
tuesday_menu := breakfast_menu(Day.tuesday)
println(tuesday_menu)
thursday_menu := breakfast_menu(Day.thursday)
println(thursday_menu)
}
Match With Enum And Else
Match With Enum And Else
Control flow structures allow your program to decide which path of execution to take. This example demonstrates the usage of Match With Enum And Else in V, showing how to control execution paths cleanly and safely.
Additional Context from Repository docs:
This example demonstrates the concepts of match with enum and else.
module main
enum Day {
sunday
monday
tuesday
wednesday
thursday
friday
saturday
}
fn weekend_breakfast_menu(day Day) string {
return match day {
.sunday {
'Cereals, Bread, Jam, Half boiled Egg'
}
.saturday {
'Milk, Bread, Fruit Bowl'
}
else {
'Sorry, we are closed on weekdays!'
}
}
}
fn main() {
sunday_menu := weekend_breakfast_menu(Day.sunday)
println(sunday_menu)
tuesday_menu := weekend_breakfast_menu(Day.tuesday)
println(tuesday_menu)
}
Bare For
Bare For
V simplifies iteration by only offering a single keyword for loops: for. There is no while keyword in V. A "bare" for (a loop declaration without any conditions or loop ranges) represents an infinite loop, which behaves exactly like while true in other languages. To exit a bare for loop, you must use a break statement inside the loop body, or exit the function using return.
This example shows how to declare a bare infinite loop.
Additional Context from Repository docs:
This example demonstrates the concepts of bare for.
module main
fn main() {
mut count := 1
// Bare for starts an infinite loop.
// WARNING: Running this without a break condition will loop forever.
for {
println('Hi ${count} times')
count += 1
// To break out, a developer would normally add:
if count > 5 {
break
}
}
}
Break For
Break For
Control flow structures allow your program to decide which path of execution to take. This example demonstrates the usage of Break For in V, showing how to control execution paths cleanly and safely.
Additional Context from Repository docs:
This example demonstrates the concepts of break for.
module main
import os
fn main() {
mut count := 0
input := os.input('Enter number of times to Greet:')
limit := input.int()
for {
if count >= limit {
break
}
println('Hi')
count += 1
}
println('Greeted Hi ${count} times')
}
Continue For
Continue For
Control flow structures allow your program to decide which path of execution to take. This example demonstrates the usage of Continue For in V, showing how to control execution paths cleanly and safely.
Additional Context from Repository docs:
This example demonstrates the concepts of continue for.
module main
fn main() {
for i in 0 .. 10 {
if i % 2 == 0 { // skips printing number that is a multiple of 2
continue
}
println(i)
}
}
For C Style
For C Style
Control flow structures allow your program to decide which path of execution to take. This example demonstrates the usage of For C Style in V, showing how to control execution paths cleanly and safely.
Additional Context from Repository docs:
This example demonstrates the concepts of for c style.
module main
fn main() {
sample := [3, 4, 23, 12, 4, 1, 45, 12, 42, 17, 92, 38]
for i := 0; i < sample.len; i += 3 {
println(sample[i])
}
}
For On Array Without Index
For On Array Without Index
Control flow structures allow your program to decide which path of execution to take. This example demonstrates the usage of For On Array Without Index in V, showing how to control execution paths cleanly and safely.
Additional Context from Repository docs:
This example demonstrates the concepts of for on array without index.
module main
fn main() {
col := [1, 2, 3, 4, 5, 6, 7]
for val in col {
if val % 2 == 0 {
println('${val} is Even')
} else {
println('${val} is Odd')
}
}
}
For On Arrays
For On Arrays
Iterating over arrays is a very common requirement. In V, you can iterate over both the index and value of each element using the for index, value in array syntax. During each iteration, the index variable (e.g., idx) contains the zero-based array index, and the element variable (e.g., ele) contains a read-only copy of the item. Both variables are scoped exclusively to the body of the loop and cannot be mutated. If you only need the element values and not their indices, V allows you to omit the index variable (e.g. for value in array).
This example declares a list of fruits and iterates over them, printing each fruit alongside its corresponding index.
Additional Context from Repository docs:
This example demonstrates the concepts of for on arrays.
module main
fn main() {
fruits := ['apple', 'banana', 'coconut']
// Loop over the indices (idx) and elements (ele) of the fruits array
for idx, ele in fruits {
println('idx: ${idx} \t fruit: ${ele}')
}
}
For On Maps
For On Maps
V makes iterating over key-value collections (maps) straightforward. By using the syntax for key, value in map, you can access both the key and the value of each entry directly during each iteration. Like arrays, the iteration variables (key and value) are local to the loop scope and are immutable. Note that iteration order over maps is not guaranteed to be stable or sorted, matching standard hash map behavior in other systems languages.
This example demonstrates how to declare a map and iterate over its key-value pairs.
Additional Context from Repository docs:
This example demonstrates the concepts of for on maps.
module main
fn main() {
// Initialize a map with string keys and integer values
lottery := {
'First': 1000
'Second': 700
'Consolation': 200
}
// Loop over keys (k) and values (v) in the 'lottery' map.
for k, v in lottery {
println('${k} prize lottery amount: ${v}')
}
}
For On Maps Ignore Key
For On Maps Ignore Key
Control flow structures allow your program to decide which path of execution to take. This example demonstrates the usage of For On Maps Ignore Key in V, showing how to control execution paths cleanly and safely.
Additional Context from Repository docs:
This example demonstrates the concepts of for on maps ignore key.
module main
fn main() {
basket := {
'apples': 10
'bananas': 12
}
mut total := 0
for _, v in basket {
total += v
}
println('Total number of fruits: ${total}')
}
For On Range
For On Range
Control flow structures allow your program to decide which path of execution to take. This example demonstrates the usage of For On Range in V, showing how to control execution paths cleanly and safely.
Additional Context from Repository docs:
This example demonstrates the concepts of for on range.
module main
fn main() {
for val in 0 .. 4 {
println(val)
}
}
For With Continue Break And Labels
For With Continue Break And Labels
Control flow structures allow your program to decide which path of execution to take. This example demonstrates the usage of For With Continue Break And Labels in V, showing how to control execution paths cleanly and safely.
Additional Context from Repository docs:
This example demonstrates the concepts of for with continue break and labels.
module main
import os
fn main() {
input := os.input('Enter the number of multiplication tables to print:')
limit := input.int()
if limit <= 0 {
return
}
first_loop: for i := 1; i <= 10; i++ {
println('Printing multiplication table for ${i}')
for j := 1; j <= 10; j++ {
mul := i * j
println('${i} * ${j} = ${mul}')
if mul >= limit * 10 {
break first_loop
}
}
println('*********')
}
}
Reverse For
Reverse For
Control flow structures allow your program to decide which path of execution to take. This example demonstrates the usage of Reverse For in V, showing how to control execution paths cleanly and safely.
Additional Context from Repository docs:
This example demonstrates the concepts of reverse for.
module main
fn main() {
subjects := ['zoology', 'chemistry', 'physics', 'algebra']
for i := subjects.len - 1; i >= 0; i-- {
println(subjects[i])
}
}
Chapter 5 Collections: Arrays and Maps
Quick Access
Below is an index of all code examples in this chapter. You can use these links to jump directly to any specific code example:
Arrays
- Declare And Initialize
- Declare Empty Array
- Declare Array With Len
- Declare Array With Init And Len
- Declare Array With Cap
- Working With Array Properties
- Access Array Elements Using Index
- Access Array Elements Using Slices
- In Operator With Array
- Append Array
- Define Fixed Size Array
- Update Fixed Size Array Elements
- Determining Type Of Fixed Array
- Slicing Fixed Size Array Results In Ordinary Array
- Declaring Multi Dimensional Arrays
- Updating Multi Dimensional Array Indices
- Reassigning Multi Dimensional Arrays
- Clone Array
- Copy Array
- Sort Integer Array
- Sort String Array
- Sort Struct Array
- Filter Array
- Filter With Anonymous Funcs On Array
- Map Array Items
- Map Using Anonymous Funcs On Array
- Array Methods
Maps
- Explicit Map Initialization
- Short Syntax Initialization Of Map
- Count Key Value Pairs In Map
- Value Given Key Of Map
- Value Given Non Existent Key Of Map
- Handling Missing Keys In Map
- Update Value Given A Key In Map
- Delete Key Value Pair From Map
- Map Methods
Collections allow you to group multiple data items together. V provides two primary built-in collection types: arrays (ordered lists of elements) and maps (key-value dictionaries). This chapter covers creating, accessing, and manipulating these collections using modern functional patterns like map and filter.
Arrays
Declare And Initialize
Declare And Initialize
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of declare and initialize.
fn main() {
mut sports := ['cricket', 'hockey', 'football']
println(sports)
}
Declare Empty Array
Declare Empty Array
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of declare empty array.
fn main() {
mut animals := []string{}
println(animals)
// prints empty array: []
animals << 'Chimpanzee'
animals << 'Dog'
println(animals)
// ['Chimpanzee', 'Dog']
}
Declare Array With Len
Declare Array With Len
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of declare array with len.
fn main() {
mut i := []int{len: 3}
println(i)
}
Declare Array With Init And Len
Declare Array With Init And Len
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of declare array with init and len.
fn main() {
mut j := []int{len: 3, init: 1}
println(j)
}
Declare Array With Cap
Declare Array With Cap
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of declare array with cap.
fn main() {
mut k := []int{cap: 2}
println(k)
}
Working With Array Properties
Working With Array Properties
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of working with array properties.
fn main() {
mut sports := ['cricket', 'hockey', 'football']
println(sports.len)
// Length of sports array
println(sports.cap)
// Capacity of sports array
println('----Deleting football----')
sports.delete(2)
// deleting football
println('Length of sports array: ${sports.len}')
println('Capacity of sports array: ${sports.cap}')
println('----Adding volleyball and baseball----')
sports << ['volleyball', 'baseball']
println(sports)
println('Length of sports array: ${sports.len}')
println('Capacity of sports array: ${sports.cap}')
}
Access Array Elements Using Index
Access Array Elements Using Index
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of access array elements using index.
fn main() {
mut sports := ['cricket', 'hockey', 'football']
s := sports[1]
println(s) // hockey
}
Access Array Elements Using Slices
Access Array Elements Using Slices
An array is a collection of elements of the same type. In V, you can retrieve a subset of elements by slicing the array. Slicing uses the [start..end] syntax, which creates a new array containing elements from the start index up to (but not including) the end index.
Positive Slicing
Positive indices represent offsets from the beginning of the array (starting at 0). For example, sports[1..3] returns a slice from index 1 to 2.
Negative Slicing
V does not support negative indices natively in slices (e.g., sports[-2..] will cause a compiler error). To achieve the effect of negative slicing (indexing from the end of the array), you calculate the start and/or end index using the array's .len property:
- Excluding the last element:
sports[..sports.len - 1](equivalent to Python'ssports[..-1]) - Range from the end:
sports[sports.len - 3 .. sports.len - 1](equivalent to Python'ssports[-3..-1]) - Last N elements:
sports[sports.len - 2 ..](equivalent to Python'ssports[-2..])
Slices are References
In V, slices are reference views of the original array, not copies.
- Modifying any element inside a slice will modify the original array.
- Assigning a slice to a variable is considered unsafe/restricted unless you wrap it in an unsafe block (mut sl := unsafe { arr[1..4] }) or clone it explicitly.
- To get a separate array slice by value (so modifications do not affect the original array), append .clone() to the end of the slice expression (mut sl_copy := arr[1..4].clone()).
Additional Context from Repository docs:
This example demonstrates the concepts of access array elements using slices, including positive slicing, .len-based negative slicing, reference mutation via unsafe blocks, and copying by value with .clone().
fn main() {
mut sports := ['cricket', 'hockey', 'football', 'basketball', 'tennis']
// Positive slicing: from index 1 to 3 (excluding index 3)
println(sports[1..3]) // ['hockey', 'football']
// V does not support negative indices natively in slices (e.g., sports[-2..] will not compile).
// To achieve "negative slicing" (indexing from the end of the array), use the `.len` property:
// Slice up to the last element (excluding it): Python's sports[..-1]
println(sports[..sports.len - 1]) // ['cricket', 'hockey', 'football', 'basketball']
// Slice from 3rd to last up to 1st to last (excluding it): Python's sports[-3..-1]
println(sports[sports.len - 3..sports.len - 1]) // ['football', 'basketball']
// Slice the last two elements: Python's sports[-2..]
println(sports[sports.len - 2..]) // ['basketball', 'tennis']
// --- REFERENCE VS VALUE BEHAVIOR ---
// In V, slices are reference views of the original array.
// If you modify an element of a slice, it affects the original array.
// Note: To prevent unsafe behavior, assigning a slice to a variable requires an `unsafe` block
// if you want it by-reference, or an explicit `.clone()` to get a copy by-value.
// 1. Modifying by reference (using unsafe)
mut original := [10, 20, 30, 40, 50]
mut ref_slice := unsafe { original[1..4] }
ref_slice[0] = 99
println(original) // [10, 99, 30, 40, 50] (Original is modified!)
// 2. Modifying by value (using .clone())
mut original_two := [10, 20, 30, 40, 50]
mut val_slice := original_two[1..4].clone()
val_slice[0] = 99
println(original_two) // [10, 20, 30, 40, 50] (Original remains unchanged!)
}
In Operator With Array
In Operator With Array
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of in operator with array.
fn main() {
odd := [1, 3, 5, 7]
println(3 in odd)
// prints: true
println(8 !in odd)
// prints: true
}
Append Array
Append Array
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of append array.
fn main() {
mut even := [2, 4, 6]
even << 8
println(even)
// prints [2, 4, 6, 8]
even << [10, 12, 14]
println(even)
// prints: [2, 4, 6, 8, 10, 12, 14]
}
Define Fixed Size Array
Define Fixed Size Array
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of define fixed size array.
fn main() {
mut fix := [4]int{}
println(fix)
// [0, 0, 0, 0]
}
Update Fixed Size Array Elements
Update Fixed Size Array Elements
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of update fixed size array elements.
fn main() {
mut fix := [4]int{}
fix[1] = 33
println(fix)
//[0, 33, 0, 0]
}
Determining Type Of Fixed Array
Determining Type Of Fixed Array
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of determining type of fixed array.
fn main() {
mut fix := [4]int{}
println(typeof(fix).name) // [4]int
}
Slicing Fixed Size Array Results In Ordinary Array
Slicing Fixed Size Array Results In Ordinary Array
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of slicing fixed size array results in ordinary array.
fn main() {
mut fix := [4]int{}
fix[1] = 33
s := fix[1..]
println(s)
// [33, 0, 0]
println(typeof(s).name) // prints: []int
}
Declaring Multi Dimensional Arrays
Declaring Multi Dimensional Arrays
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of declaring multi dimensional arrays.
fn main() {
mut coordinates_2d := [][]int{len: 4, init: []int{len: 2}}
println(typeof(coordinates_2d).name)
// [][]int
println(coordinates_2d)
// [[0, 0], [0, 0], [0, 0], [0, 0]]
}
Updating Multi Dimensional Array Indices
Updating Multi Dimensional Array Indices
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of updating multi dimensional arrays.
fn main() {
mut coordinates_2d := [][]int{len: 4, init: []int{len: 2}}
println(coordinates_2d.len)
point_1 := [0, 0]
point_2 := [0, 1]
point_3 := [1, 0]
point_4 := [1, 1]
coordinates_2d[0] = point_1
coordinates_2d[1] = point_2
coordinates_2d[2] = point_3
coordinates_2d[3] = point_4
println(coordinates_2d)
}
Reassigning Multi Dimensional Arrays
Reassigning Multi Dimensional Arrays
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of updating multi dimensional arrays.
fn main() {
mut coordinates_2d := [][]int{len: 4, init: []int{len: 2}}
println(coordinates_2d.len)
coordinates_2d = [
[0, 0],
[0, 1],
[1, 0],
[1, 1],
]
println(coordinates_2d)
}
Clone Array
Clone Array
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of clone array.
fn main() {
r := [1, 2, 3, 4]
mut u := r.clone()
// copies the array r to u
println(u)
}
Copy Array
Copy Array
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of copy array.
fn main() {
r := [1, 2, 3, 4]
s := unsafe { r }
println(s)
unsafe {
r.free()
}
}
Sort Integer Array
Sort Integer Array
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of sort integer array.
fn main() {
mut i := [3, 2, 8, 1]
i.sort()
// ascending order
println(i)
i.sort(a > b)
// descending order
println(i)
}
Sort String Array
Sort String Array
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of sort string array.
fn main() {
mut fruits := ['Apples', 'avocado', 'banana', 'Orange']
fruits.sort()
// ascending order
println(fruits)
fruits.sort(a > b)
// reverse order
println(fruits)
}
Sort Struct Array
Sort Struct Array
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of sort struct array.
module main
struct Student {
id int
name string
class int
}
fn main() {
// Declare an empty array
mut students := []Student{}
// Create students
st1 := Student{
id: 1
name: 'Ram'
class: 9
}
st2 := Student{
id: 2
name: 'Katy'
class: 3
}
st3 := Student{
id: 3
name: 'Tom'
class: 6
}
// Append all the students to the array
students << [st1, st2, st3]
println(students)
// Reverse Sort students by id
students.sort(a.id > b.id)
println('Students sorted in reverse order of id:')
println(students)
// Sort students by class in ascending order
students.sort(a.class < b.class)
println('Students sorted in ascending order of class:')
println(students)
// Sort students by name in reverse order
students.sort(a.name > b.name)
println('Students sorted in reverse order of name:')
println(students)
}
Filter Array
Filter Array
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of filter array.
fn main() {
f := [1, 2, 3, 4, 5, 6, 7, 8, 9]
multiples_of_3 := f.filter(it % 3 == 0)
println(multiples_of_3)
// [3, 6, 9]
}
Filter With Anonymous Funcs On Array
Filter With Anonymous Funcs On Array
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of filter with anonymous funcs on array.
fn main() {
fruits := ['apple', 'mango', 'water melon', 'musk melon']
fruits_starting_m := fruits.filter(fn (f string) bool {
return f.starts_with('m')
})
println(fruits_starting_m)
}
Map Array Items
Map Array Items
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of map array items.
fn main() {
visitor := ['Tom', 'Ram', 'Rao']
res := visitor.map('Mr. ' + it)
println(res)
}
Map Using Anonymous Funcs On Array
Map Using Anonymous Funcs On Array
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of map using anonymous funcs on array.
fn main() {
colors := ['red', 'blue', 'green', 'white', 'black']
colors_with_letter_e := colors.map(fn (c string) int {
if c.contains('e') { return 1 } else { return 0 }
})
println(colors_with_letter_e)
}
Array Methods
Array Methods
An array is a collection of elements of the same type. In V, arrays are declared using square brackets. They are index-based, dynamically sized, and provide built-in methods like map(), filter(), and sort() for functional-style manipulation.
These examples show how to initialize, append, clone, copy, and manipulate arrays.
Additional Context from Repository docs:
This example demonstrates the concepts of array methods.
module main
// A custom comparison function for sorting.
// It accepts references to elements (e.g. &int) and returns -1, 1, or 0.
fn compare_ints(a &int, b &int) int {
val_a := *a
val_b := *b
if val_a < val_b {
return -1
}
if val_a > val_b {
return 1
}
return 0
}
fn main() {
println('--- Array Built-in Methods ---')
// 1. ensure_cap(required)
// Ensures that the array has at least the specified capacity.
mut a := [10, 20, 30]
a.ensure_cap(10)
println('ensure_cap: cap is ${a.cap >= 10}') // true
// 2. repeat(count)
// Repeats the array count times and returns a new array.
rep := a.repeat(2)
println('repeat: ${rep}') // [10, 20, 30, 10, 20, 30]
// 3. repeat_to_depth(count, depth) (unsafe)
// Recursively repeats a multi-dimensional array count times to the specified depth.
grid := [[1, 2], [3, 4]]
unsafe {
rep_grid := grid.repeat_to_depth(2, 1)
// Cast the raw array struct back to typed [][]int
typed_grid := *(&[][]int(&rep_grid))
println('repeat_to_depth: ${typed_grid}') // [[1, 2], [3, 4], [1, 2], [3, 4]]
rep_grid.free()
}
// 4. insert(index, val)
// Inserts a new element at the specified index.
a.insert(1, 15)
println('insert: ${a}') // [10, 15, 20, 30]
// 5. prepend(val)
// Prepends a new element at the beginning of the array.
a.prepend(5)
println('prepend: ${a}') // [5, 10, 15, 20, 30]
// 6. delete(index)
// Deletes the element at the specified index.
a.delete(1) // Deletes index 1 (which is 10)
println('delete: ${a}') // [5, 15, 20, 30]
// 7. delete_many(index, size)
// Deletes size elements starting from the specified index.
a.delete_many(1, 2) // Deletes 2 elements starting at index 1
println('delete_many: ${a}') // [5, 30]
// 8. clear()
// Sets the array length to 0, retaining capacity.
mut a_clear := [1, 2, 3]
a_clear.clear()
println('clear: len is ${a_clear.len}') // 0
// 9. reset() (unsafe)
// Sets all elements of the array to 0 / empty values without altering len or cap.
mut a_reset := [1, 2, 3]
unsafe {
a_reset.reset()
}
println('reset: ${a_reset}') // [0, 0, 0]
unsafe {
a_reset.free()
}
// 10. trim(index)
// Truncates the array length to index.
mut a_trim := [1, 2, 3, 4]
a_trim.trim(2)
println('trim: ${a_trim}') // [1, 2]
// 11. drop(num)
// Drops the first num elements in-place.
mut a_drop := [1, 2, 3, 4]
a_drop.drop(2)
println('drop: ${a_drop}') // [3, 4]
// 12. first()
// Returns the first element of the array.
println('first: ${a_drop.first()}') // 3
// 13. last()
// Returns the last element of the array.
println('last: ${a_drop.last()}') // 4
// 14. pop_left()
// Removes and returns the first element of the array.
mut a_pop := [1, 2, 3]
first_val := a_pop.pop_left()
println('pop_left: value = ${first_val}, array = ${a_pop}') // 1, [2, 3]
// 15. pop()
// Removes and returns the last element of the array.
last_val := a_pop.pop()
println('pop: value = ${last_val}, array = ${a_pop}') // 3, [2]
// 16. delete_last()
// Deletes the last element of the array.
mut a_del_last := [1, 2, 3]
a_del_last.delete_last()
println('delete_last: ${a_del_last}') // [1, 2]
// 17. clone()
// Returns a deep copy of the array.
a_clone := a_del_last.clone()
println('clone: ${a_clone}') // [1, 2]
// 18. clone_to_depth(depth) (unsafe)
// Recursively clones a multi-dimensional array up to the specified depth.
grid2 := [[1, 2], [3, 4]]
unsafe {
grid_clone := grid2.clone_to_depth(1)
typed_clone := *(&[][]int(&grid_clone))
println('clone_to_depth: ${typed_clone}') // [[1, 2], [3, 4]]
grid_clone.free()
}
// 19. push_many(val, size) (unsafe)
// Appends size elements starting from a raw pointer val to the array.
mut a_push := [1, 2]
vals := [3, 4]
unsafe {
a_push.push_many(vals.data, 2)
}
println('push_many: ${a_push}') // [1, 2, 3, 4]
unsafe {
a_push.free()
vals.free()
}
// 20. reverse()
// Returns a new reversed copy of the array.
a_rev := [1, 2, 3]
println('reverse: ${a_rev.reverse()}') // [3, 2, 1]
// 21. reverse_in_place()
// Reverses the array elements in-place.
mut a_rev_ip := [1, 2, 3]
a_rev_ip.reverse_in_place()
println('reverse_in_place: ${a_rev_ip}') // [3, 2, 1]
// 22. free() (unsafe)
// Deallocates the array's buffer.
mut a_free := [1, 2, 3]
unsafe {
a_free.free()
}
println('free: array freed')
// 23. filter(it)
// Filters elements that satisfy a predicate using compiler-defined `it` expression.
a_filt := [1, 2, 3, 4]
filtered := a_filt.filter(it % 2 == 0)
println('filter: ${filtered}') // [2, 4]
// 24. any(it)
// Checks if any element satisfies the predicate.
println('any: ${a_filt.any(it > 3)}') // true
// 25. count(it)
// Counts how many elements satisfy the predicate.
println('count: ${a_filt.count(it % 2 == 0)}') // 2
// 26. all(it)
// Checks if all elements satisfy the predicate.
println('all: ${a_filt.all(it > 0)}') // true
// 27. map(it)
// Maps elements to a new array using a transformation expression.
mapped := a_filt.map(it * 10)
println('map: ${mapped}') // [10, 20, 30, 40]
// 28. sort() & sort(custom)
// Sorts elements in-place. Uses optional boolean expression for custom order (uses magic vars a and b).
mut a_sort := [3, 1, 4, 2]
a_sort.sort()
println('sort (default ascending): ${a_sort}') // [1, 2, 3, 4]
a_sort.sort(a > b)
println('sort (custom descending): ${a_sort}') // [4, 3, 2, 1]
// 29. sorted() & sorted(custom)
// Returns a sorted copy of the array. Uses optional boolean expression for custom order (uses magic vars a and b).
a_sorted := [3, 1, 4, 2]
println('sorted (default): ${a_sorted.sorted()}') // [1, 2, 3, 4]
println('sorted (custom): ${a_sorted.sorted(a > b)}') // [4, 3, 2, 1]
// 30. sort_with_compare(callback)
// Sorts the array in-place using a custom comparison function.
mut a_compare := [3, 1, 4, 2]
a_compare.sort_with_compare(compare_ints)
println('sort_with_compare: ${a_compare}') // [1, 2, 3, 4]
// 31. sorted_with_compare(callback)
// Returns a sorted copy of the array using a custom comparison function.
a_sorted_comp := [3, 1, 4, 2]
println('sorted_with_compare: ${a_sorted_comp.sorted_with_compare(compare_ints)}') // [1, 2, 3, 4]
// 32. contains(value)
// Checks if the array contains value.
println('contains: ${a_filt.contains(3)}') // true
// 33. index(value)
// Returns the index of the first occurrence of value, or -1 if not found.
println('index: ${a_filt.index(3)}') // 2
// 34. last_index(value)
// Returns the index of the last occurrence of value, or -1 if not found.
a_dup := [1, 2, 3, 2]
println('last_index: ${a_dup.last_index(2)}') // 3
// 35. grow_cap(amount)
// Increases the array capacity by the specified amount.
mut a_grow := [1, 2]
a_grow.grow_cap(10)
println('grow_cap: cap is ${a_grow.cap >= 12}') // true
// 36. grow_len(amount) (unsafe)
// Increases the array length by the specified amount.
unsafe {
a_grow.grow_len(3)
}
println('grow_len: ${a_grow}') // [1, 2, 0, 0, 0]
unsafe {
a_grow.free()
}
// 37. pointers() (unsafe)
// Returns an array of void pointers (pointers()) pointing to each element.
a_ptrs := [10, 20]
unsafe {
ptrs := a_ptrs.pointers()
println('pointers (first element): ${*(&int(ptrs[0]))}') // 10
ptrs.free()
a_ptrs.free()
}
}
Array Update Syntax
Array Update Syntax
V lets you initialise an array by spreading an existing array (using ellipsis spread syntax ...), optionally followed by additional elements.
In the official V specification, spreading is written as [...base, 3, 4]. This creates a copy/modified version of the array without mutating the original variable. In version 0.5.1, you can achieve the equivalent functionality by cloning the array and appending elements.
module main
fn main() {
println('=== Array Update Syntax ===')
// NOTE: Array spread update syntax `[...base, 3, 4]` is defined in the V language specification (docs.md)
// but is not fully supported in V 0.5.1 parser.
// Below is the specification representation:
/*
base := [1, 2]
a := [...base, 3, 4]
assert a == [1, 2, 3, 4]
*/
// Equivalent cloning & appending representation for V 0.5.1:
base := [1, 2]
mut a := base.clone()
a << 3
a << 4
println('base: ${base}') // [1, 2]
println('a: ${a}') // [1, 2, 3, 4]
assert a == [1, 2, 3, 4]
}
Maps
Explicit Map Initialization
Explicit Map Initialization
A map is an unordered collection of key-value pairs, also known as a dictionary or associative array. In V, map keys must be strings or integer types, and values can be of any type. Maps are declared using curly braces with colon separators.
These examples cover how to initialize maps, look up keys, add or delete entries, and check if a key exists.
Additional Context from Repository docs:
This example demonstrates the concepts of explicit map initialization.
fn main() {
mut books := map[string]int{}
books['V on Wheels'] = 320
books['Go for Dummies'] = 279
println(books)
}
Short Syntax Initialization Of Map
Short Syntax Initialization Of Map
A map is an unordered collection of key-value pairs, also known as a dictionary or associative array. In V, map keys must be strings or integer types, and values can be of any type. Maps are declared using curly braces with colon separators.
These examples cover how to initialize maps, look up keys, add or delete entries, and check if a key exists.
Additional Context from Repository docs:
This example demonstrates the concepts of short syntax initialization of map.
fn main() {
mut student_1 := {
'english': 90
'mathematics': 96
'physics': 83
'chemistry': 89
}
println(student_1)
}
Count Key Value Pairs In Map
Count Key Value Pairs In Map
A map is an unordered collection of key-value pairs, also known as a dictionary or associative array. In V, map keys must be strings or integer types, and values can be of any type. Maps are declared using curly braces with colon separators.
These examples cover how to initialize maps, look up keys, add or delete entries, and check if a key exists.
Additional Context from Repository docs:
This example demonstrates the concepts of count key value pairs in map.
fn main() {
mut student_1 := {
'english': 90
'mathematics': 96
'physics': 83
'chemistry': 89
}
cnt := student_1.len
println('There are ${cnt} key-value pairs in student_1 map')
}
Value Given Key Of Map
Value Given Key Of Map
A map is an unordered collection of key-value pairs, also known as a dictionary or associative array. In V, map keys must be strings or integer types, and values can be of any type. Maps are declared using curly braces with colon separators.
These examples cover how to initialize maps, look up keys, add or delete entries, and check if a key exists.
Additional Context from Repository docs:
This example demonstrates the concepts of value given key of map.
fn main() {
mut student_1 := {
'english': 90
'mathematics': 96
'physics': 83
'chemistry': 89
}
println(student_1['physics']) // 83
}
Value Given Non Existent Key Of Map
Value Given Non Existent Key Of Map
A map is an unordered collection of key-value pairs, also known as a dictionary or associative array. In V, map keys must be strings or integer types, and values can be of any type. Maps are declared using curly braces with colon separators.
These examples cover how to initialize maps, look up keys, add or delete entries, and check if a key exists.
Additional Context from Repository docs:
This example demonstrates the concepts of value given non existent key of map.
fn main() {
mut student_1 := {
'english': 90
'mathematics': 96
'physics': 83
'chemistry': 89
}
println(student_1['geography']) // 0
}
Handling Missing Keys In Map
Handling Missing Keys In Map
A map is an unordered collection of key-value pairs, also known as a dictionary or associative array. In V, map keys must be strings or integer types, and values can be of any type. Maps are declared using curly braces with colon separators.
These examples cover how to initialize maps, look up keys, add or delete entries, and check if a key exists.
Additional Context from Repository docs:
This example demonstrates the concepts of handling missing keys in map.
fn main() {
mut student_1 := {
'english': 90
'mathematics': 96
'physics': 83
'chemistry': 89
}
sub := 'geography'
res := student_1[sub] or { panic('marks for subject ${sub} not yet updated') } // throws error
}
Update Value Given A Key In Map
Update Value Given A Key In Map
A map is an unordered collection of key-value pairs, also known as a dictionary or associative array. In V, map keys must be strings or integer types, and values can be of any type. Maps are declared using curly braces with colon separators.
These examples cover how to initialize maps, look up keys, add or delete entries, and check if a key exists.
Additional Context from Repository docs:
This example demonstrates the concepts of update value given a key in map.
fn main() {
mut student_1 := {
'english': 90
'mathematics': 96
'physics': 83
'chemistry': 89
}
student_1['english'] = 93
println(student_1)
}
Delete Key Value Pair From Map
Delete Key Value Pair From Map
A map is an unordered collection of key-value pairs, also known as a dictionary or associative array. In V, map keys must be strings or integer types, and values can be of any type. Maps are declared using curly braces with colon separators.
These examples cover how to initialize maps, look up keys, add or delete entries, and check if a key exists.
Additional Context from Repository docs:
This example demonstrates the concepts of delete key value pair from map.
fn main() {
mut student_1 := {
'english': 90
'mathematics': 96
'physics': 83
'chemistry': 89
}
println('Key-Value pairs before deleting a key: ${student_1.len}')
student_1.delete('physics')
println('Key-Value pairs after deleting a key ${student_1.len}')
}
Map Methods
Map Methods
A map is an unordered collection of key-value pairs, also known as a dictionary or associative array. In V, map keys must be strings or integer types, and values can be of any type. Maps are declared using curly braces with colon separators.
These examples cover how to initialize maps, look up keys, add or delete entries, and check if a key exists.
Additional Context from Repository docs:
This example demonstrates the concepts of map methods.
module main
fn main() {
println('--- Map Built-in Methods ---')
mut m := {
'one': 1
'two': 2
}
println('initial map: ${m}') // {"one": 1, "two": 2}
// 1. keys()
// Returns an array containing all keys in the map.
println('keys: ${m.keys()}') // ["one", "two"]
// 2. values()
// Returns an array containing all values in the map.
println('values: ${m.values()}') // [1, 2]
// 3. clone()
// Returns a deep copy of the map.
mut m_clone := m.clone()
println('clone: ${m_clone}') // {"one": 1, "two": 2}
// 4. delete(key)
// Removes a key-value pair from the map by key.
m.delete('one')
println('delete: ${m}') // {"two": 2}
// 5. reserve(capacity)
// Pre-allocates space for at least capacity elements in the map.
m.reserve(10)
println('reserve: reserved capacity successfully')
// 6. clear()
// Removes all key-value pairs from the map without deallocating data.
m.clear()
println('clear: len is ${m.len}') // 0
// 7. move()
// Moves the map contents to a new map variable and clears the original map to empty.
mut m_move := {
'three': 3
'four': 4
}
moved := m_move.move()
println('move (new map): ${moved}') // {"three": 3, "four": 4}
println('move (original map): ${m_move}') // {}
// 8. free() (unsafe)
// Deallocates the map memory.
mut m_free := {
'temp': 100
}
unsafe {
m_free.free()
}
println('free: map freed successfully')
}
Import Maps Helpers
Import Maps Helpers
The maps module provides higher-level helpers for filtering, transforming, inverting, merging, and converting between maps and arrays. These helpers are useful when you want to work with map data in a functional style without manually writing the loops yourself.
Additional Context from Repository docs:
This example demonstrates the concepts of import maps helpers.
module main
import maps
fn main() {
println('--- Import Maps Module Helpers ---')
// Start with a simple map of fruit counts.
m1 := {
'apple': 1
'banana': 2
'cherry': 3
}
// filter() keeps only the entries that satisfy the callback condition.
filtered := maps.filter(m1, fn (k string, v int) bool {
return v > 1
})
println('filter(): ${filtered}')
// to_array() builds a new array by transforming each entry.
keys_upper := maps.to_array(m1, fn (k string, v int) string {
return k.to_upper()
})
println('to_array(): ${keys_upper}')
// invert() swaps each key/value pair so the values become the keys.
inverted := maps.invert(m1)
println('invert(): ${inverted}')
// from_array() creates a map from a list of strings.
fruits := ['apple', 'banana', 'cherry']
map_from_arr := maps.from_array(fruits)
println('from_array(): ${map_from_arr}')
// merge() combines two maps and lets the second map override duplicates.
m2 := {
'banana': 20
'date': 4
}
merged := maps.merge(m1, m2)
println('merge(): ${merged}')
// merge_in_place() mutates the first map directly.
mut mut_map := {
'a': 1
}
maps.merge_in_place(mut mut_map, {
'b': 2
'c': 3
})
println('merge_in_place(): ${mut_map}')
// flat_map() can expand each entry into multiple output values.
flat_items := maps.flat_map[string, int, string](m1, fn (k string, v int) []string {
return [k, v.str()]
})
println('flat_map(): ${flat_items}')
// to_map() transforms each entry into a new key/value pair.
transformed := maps.to_map[string, int, string, int](m1, fn (k string, v int) (string, int) {
return k.to_upper(), v * 10
})
println('to_map(): ${transformed}')
}
Map Update Syntax
Map Update Syntax
Similar to structs, V lets you initialise a new map with updates applied on top of an existing map using the spread syntax .... This is functionally equivalent to cloning the map and updating it, except that it avoids declaring a mutable intermediate variable and allows inlining elements.
module main
fn main() {
println('=== Map Update Syntax ===')
base_map := {
'a': 4
'b': 5
}
// Create a new map by updating elements of base_map
foo := {
...base_map
'b': 88
'c': 99
}
println('base_map: ${base_map}') // {'a': 4, 'b': 5}
println('foo: ${foo}') // {'a': 4, 'b': 88, 'c': 99}
assert base_map['b'] == 5
assert foo['a'] == 4
assert foo['b'] == 88
assert foo['c'] == 99
}
Chapter 6 Functions
Quick Access
Below is an index of all code examples in this chapter. You can use these links to jump directly to any specific code example:
Advanced Function Features
- Function Returns Value Example 1
- Function Returns Value Example 2
- Funtions Without Return Type
- Function With Input Arguments
- Function Return Multiple Values
- Ignore Function Return Value
- Function Calls Other Function
- Example 1
- Example 2
- Error Script Functions
- Script Functions
- Functions Module Variables - Main (main.v)
- Mymod
- Functions With Optional Return Types Example 1
- Function With Optional Return Type Example 2
- Mod1
- Public Function Demo1
- Public Function Demo2
- Public Function Demo3
- Function With Defer Block
- Functions As Elements Of Array Or Map
Function Extras
- Hello
- Basic Functions
- Anonymous Functions
- Functions As Input Arguments
- Functions That Return Other Functions
- Lambda Expressions
- Closures
Functions let you turn repeated or complex logic into small, named building blocks. A useful mental model is: define the task, give it inputs if needed, do the work, and return a useful result. This chapter starts with simple functions and then introduces multiple returns, optional results, higher-order functions, and cleanup with defer.
Advanced Function Features
Function Returns Value Example 1
Function Returns Value Example 1
A simple function is a small helper that turns inputs into a useful result. The basic pattern is: define the function, pass in values, do some work, and return the answer. In this example, add takes two integers and returns their sum.
Additional Context from Repository docs:
This example demonstrates the concepts of function returns value example 1.
fn add(a int, b int) int {
return a + b
}
fn main() {
println(add(2, 3))
}
Function Returns Value Example 2
Function Returns Value Example 2
A function does not need to print anything itself. It can build a value and hand it back to the caller. Here, say_hello returns a greeting string, and main decides how to display it.
Additional Context from Repository docs:
This example demonstrates the concepts of function returns value example 2.
fn say_hello() string {
return 'Hello!'
}
fn main() {
// call the method
res := say_hello()
println(res)
// prints: Hello!
}
Functions Without Return Type
Functions Without Return Type
Some functions are used for actions rather than calculations. They may print output, write files, or update state. In that case, you can leave out the return type and focus on the side effect.
Additional Context from Repository docs:
This example demonstrates the concepts of funtions without return type.
fn console_greeter() {
println('Hello!')
}
fn main() {
console_greeter()
// prints: Hello!
}
Function With Input Arguments
Function With Input Arguments
Functions become much more useful when they accept inputs. This example uses two numbers as arguments and returns their sum, showing the classic input → process → output flow.
Additional Context from Repository docs:
This example demonstrates the concepts of function with input arguments.
fn add(a int, b int) int {
return a + b
}
fn main() {
res := add(2, 4)
println(res)
// prints: 6
}
Function Return Multiple Values
Function Return Multiple Values
In V, functions are not limited to returning a single value. A function can return a tuple containing two or more values when they logically belong together. A very common pattern in systems programming is returning both the main result of an operation and a secondary value (such as status flags, byte counts, or errors). Returning multiple values is clean, avoids wrapping results in temporary struct containers, and is assigned using parallel declaration at the call site.
This example shows how a function computes a string greeting and returns both the greeting string and its length as an integer.
Additional Context from Repository docs:
This example demonstrates the concepts of function return multiple values.
// The return types are declared within parentheses: (string, int)
fn greet_and_message_length(name string) (string, int) {
mut greeting := 'Hello, ' + name + '!'
// Multiple values are returned as a comma-separated list
return greeting, greeting.len
}
fn main() {
// Receive multiple return values using parallel assignment
i, j := greet_and_message_length('Navule')
println(i) // Prints: Hello, Navule!
println(j) // Prints the length: 14
}
Ignore Function Return Value
Ignore Function Return Value
Sometimes you only care about one returned value. V lets you ignore the rest with _, which keeps the code readable when you are only interested in part of the result.
Additional Context from Repository docs:
This example demonstrates the concepts of ignore function return value.
fn greet_and_message_length(name string) (string, int) {
mut greeting := 'Hello, ' + name + '!'
return greeting, greeting.len
}
fn main() {
i, _ := greet_and_message_length('Navule')
println(i)
}
Function Calls Other Function
Function Calls Other Function
Functions can call other functions to split a bigger problem into smaller steps. This makes the code easier to understand and easier to reuse later.
Additional Context from Repository docs:
This example demonstrates the concepts of function calls other function.
fn greet(p string) string {
return 'Hello, ${p}!'
}
fn welcome(p string) string {
msg := 'Nice to meet you!'
mut g := greet(p)
g = g + ' ${msg}'
return g
}
fn main() {
res := welcome('Visitor')
println(res)
}
Example 1
Example 1
V functions support several advanced features:
- Multiple Return Values: A function can return more than one value (often a result and an error).
- Blank Identifier (
_): Used to discard unwanted return values. - Defer: Schedules a block of code to run right before the function exits, which is excellent for resource cleanup.
- Anonymous Functions & Closures: Functions defined inline that can capture variables from their outer scope.
These examples illustrate these powerful concepts.
Additional Context from Repository docs:
This example demonstrates the concepts of example 1.
fn increment_array_items(arr []int, inc int) []int {
mut tmp := arr.clone()
for mut i in tmp {
i += inc
}
return tmp
}
fn main() {
a := [5, 6]
res := increment_array_items(a, 100)
println('a: ${a}')
println('res: ${res}')
}
Example 2
Example 2
V functions support several advanced features:
- Multiple Return Values: A function can return more than one value (often a result and an error).
- Blank Identifier (
_): Used to discard unwanted return values. - Defer: Schedules a block of code to run right before the function exits, which is excellent for resource cleanup.
- Anonymous Functions & Closures: Functions defined inline that can capture variables from their outer scope.
These examples illustrate these powerful concepts.
Additional Context from Repository docs:
This example demonstrates the concepts of example 2.
fn increment_array_items(mut arr []int, inc int) {
for mut i in arr {
i += inc
}
}
fn main() {
mut a := [5, 6]
increment_array_items(mut a, 100)
// Must specify mut keyword when sending value to mut arg of a function
println('a: ${a}')
}
Error Script Functions
Error Script Functions
V functions support several advanced features:
- Multiple Return Values: A function can return more than one value (often a result and an error).
- Blank Identifier (
_): Used to discard unwanted return values. - Defer: Schedules a block of code to run right before the function exits, which is excellent for resource cleanup.
- Anonymous Functions & Closures: Functions defined inline that can capture variables from their outer scope.
These examples illustrate these powerful concepts.
#!/usr/local/bin/v run
cnt := 2
for i in 0 .. cnt {
log('iteration ${i}')
}
fn log(msg string) {
println(msg)
}
Script Functions
Script Functions
V functions support several advanced features:
- Multiple Return Values: A function can return more than one value (often a result and an error).
- Blank Identifier (
_): Used to discard unwanted return values. - Defer: Schedules a block of code to run right before the function exits, which is excellent for resource cleanup.
- Anonymous Functions & Closures: Functions defined inline that can capture variables from their outer scope.
These examples illustrate these powerful concepts.
#!/usr/local/bin/v run
fn log(msg string) {
println(msg)
}
cnt := 2
for i in 0 .. cnt {
log('iteration ${i}')
}
Functions Module Variables - Main (main.v)
Functions Module Variables - Main
V functions support several advanced features:
- Multiple Return Values: A function can return more than one value (often a result and an error).
- Blank Identifier (
_): Used to discard unwanted return values. - Defer: Schedules a block of code to run right before the function exits, which is excellent for resource cleanup.
- Anonymous Functions & Closures: Functions defined inline that can capture variables from their outer scope.
These examples illustrate these powerful concepts.
Additional Context from Repository docs:
This example demonstrates the concepts of main.
// file: main.v
module main
import mymod
fn main() {
mymod.msg := 'global variable demo'
println(mymod.msg)
}
Mymod
Mymod
V functions support several advanced features:
- Multiple Return Values: A function can return more than one value (often a result and an error).
- Blank Identifier (
_): Used to discard unwanted return values. - Defer: Schedules a block of code to run right before the function exits, which is excellent for resource cleanup.
- Anonymous Functions & Closures: Functions defined inline that can capture variables from their outer scope.
These examples illustrate these powerful concepts.
Additional Context from Repository docs:
This example demonstrates the concepts of mymod.
// file: mymod/mymod.v
module mymod
__global (
msg string
)
Functions With Optional Return Types Example 1
Functions With Optional Return Types Example 1
V functions support several advanced features:
- Multiple Return Values: A function can return more than one value (often a result and an error).
- Blank Identifier (
_): Used to discard unwanted return values. - Defer: Schedules a block of code to run right before the function exits, which is excellent for resource cleanup.
- Anonymous Functions & Closures: Functions defined inline that can capture variables from their outer scope.
These examples illustrate these powerful concepts.
Additional Context from Repository docs:
This example demonstrates the concepts of functions with optional return types example 1.
module main
fn is_teen(age int) ?string {
if age < 0 {
return none
} else if age >= 13 && age <= 19 {
return 'teenager'
} else {
return 'not teenager'
}
}
fn main() {
x := is_teen(-3) or { 'invalid age provided' }
println(x)
}
Function With Optional Return Type Example 2
Function With Optional Return Type Example 2
V functions support several advanced features:
- Multiple Return Values: A function can return more than one value (often a result and an error).
- Blank Identifier (
_): Used to discard unwanted return values. - Defer: Schedules a block of code to run right before the function exits, which is excellent for resource cleanup.
- Anonymous Functions & Closures: Functions defined inline that can capture variables from their outer scope.
These examples illustrate these powerful concepts.
Additional Context from Repository docs:
This example demonstrates the concepts of function with optional return type example 2.
module main
fn is_teen(age int) !string {
if age < 0 {
return error('invalid age provided')
} else if age >= 13 && age <= 19 {
return 'teenager'
} else {
return 'not teenager'
}
}
fn main() {
x := is_teen(-3) or { err.msg() }
println(x)
}
Mod1
Mod1
V functions support several advanced features:
- Multiple Return Values: A function can return more than one value (often a result and an error).
- Blank Identifier (
_): Used to discard unwanted return values. - Defer: Schedules a block of code to run right before the function exits, which is excellent for resource cleanup.
- Anonymous Functions & Closures: Functions defined inline that can capture variables from their outer scope.
These examples illustrate these powerful concepts.
Additional Context from Repository docs:
This example demonstrates the concepts of mod1.
// file: mod1/mod1.v
module mod1
fn greet1() string {
return 'Hello from greet1'
}
pub fn greet2() string {
return 'Hello from greet2'
}
pub fn greet_and_wish() string {
wish := 'Have a nice day!'
return greet1() + ', ' + wish
}
Public Function Demo1
Public Function Demo1
V functions support several advanced features:
- Multiple Return Values: A function can return more than one value (often a result and an error).
- Blank Identifier (
_): Used to discard unwanted return values. - Defer: Schedules a block of code to run right before the function exits, which is excellent for resource cleanup.
- Anonymous Functions & Closures: Functions defined inline that can capture variables from their outer scope.
These examples illustrate these powerful concepts.
Additional Context from Repository docs:
This example demonstrates the concepts of public function demo1.
// file: public_function_demo1.v
import mod1
fn main() {
g := mod1.greet1()
println(g)
}
Public Function Demo2
Public Function Demo2
V functions support several advanced features:
- Multiple Return Values: A function can return more than one value (often a result and an error).
- Blank Identifier (
_): Used to discard unwanted return values. - Defer: Schedules a block of code to run right before the function exits, which is excellent for resource cleanup.
- Anonymous Functions & Closures: Functions defined inline that can capture variables from their outer scope.
These examples illustrate these powerful concepts.
Additional Context from Repository docs:
This example demonstrates the concepts of public function demo2.
// file: public_function_demo2.v
import mod1
fn main() {
g := mod1.greet2()
println(g)
}
Public Function Demo3
Public Function Demo3
V functions support several advanced features:
- Multiple Return Values: A function can return more than one value (often a result and an error).
- Blank Identifier (
_): Used to discard unwanted return values. - Defer: Schedules a block of code to run right before the function exits, which is excellent for resource cleanup.
- Anonymous Functions & Closures: Functions defined inline that can capture variables from their outer scope.
These examples illustrate these powerful concepts.
Additional Context from Repository docs:
This example demonstrates the concepts of public function demo3.
// file: public_function_demo3.v
import mod1
fn main() {
g := mod1.greet_and_wish()
println(g)
}
Function With Defer Block
Function With Defer Block
The defer keyword is a crucial feature for resource safety and cleanups. A defer block schedules a block of code to run automatically right before the containing function exits, regardless of which return path is taken. If there are multiple defer blocks in a function, they are executed in reverse order of their declaration (Last-In, First-Out). This ensures resources (like file descriptors, database connections, or socket connections) are closed safely without duplicating cleanup code across every return statement.
This example illustrates the execution sequence of statements inside a function containing a defer block.
Additional Context from Repository docs:
This example demonstrates the concepts of function with defer block.
module main
fn void_func_defer() {
println('Hello')
// This block is scheduled to execute at the very end of the function scope.
defer {
println('Hi from defer block')
}
println('How are you?')
// The function ends here, triggering the deferred block execution automatically.
}
fn main() {
void_func_defer()
}
Functions As Elements Of Array Or Map
Functions As Elements Of Array Or Map
Functions can be stored in arrays and maps just like other values. This allows you to choose an operation dynamically at runtime, which is helpful in flexible programs.
Additional Context from Repository docs:
This example demonstrates the concepts of functions as elements of array or map.
module main
fn adder(i int, j int) int {
return i + j
}
fn subtractor(i int, j int) int {
return i - j
}
fn multiplier(i int, j int) int {
return i * j
}
fn main() {
i, j := 2, 5
println('Functions as elements of an Array')
funcs := [adder, subtractor, multiplier]
for f in funcs {
res := f(i, j)
println(res)
}
println('Functions as elements of Map')
d := {
'sum': adder
'difference': subtractor
'product': multiplier
}
for key, val in d {
res := val(i, j)
println('${key} of ${i} and ${j}: ${res}')
}
}
Function Extras
Hello
Hello
Every V program starts with main(). It is the entry point where execution begins, so it is the first function most beginners learn.
Additional Context from Repository docs:
This example demonstrates the concepts of hello.
module main
fn main() {
println('Welcome to the World of V!')
}
Basic Functions
Basic Functions
A basic function packages a task so you can call it later instead of repeating the same code. This example shows a function that prints a message when invoked.
Additional Context from Repository docs:
This example demonstrates the concepts of basic functions.
// Define a simple function that prints a greeting.
fn greet(name string) {
println('Hello, ${name}!')
}
fn main() {
// Call the function with different argument values.
greet('Ada')
greet('Grace')
}
Anonymous Functions
Anonymous Functions
Anonymous functions (also known as lambda functions or function literals) are functions that are defined inline without a name. In V, functions are first-class citizens, meaning they can be assigned to variables, passed as arguments to other functions, or returned from functions. Anonymous functions are highly useful for short-lived, one-off operations such as custom sort criteria, filter callbacks, or event handlers.
This example shows how to declare an anonymous function, assign it to a variable, and call it.
Additional Context from Repository docs:
This example demonstrates the concepts of anonymous functions.
module main
fn main() {
// Define an anonymous function using the 'fn' keyword without a name.
// Assign it directly to the local variable 'greet'.
greet := fn (name string) {
println('Hello, ${name}')
}
// Invoke the anonymous function by calling the variable as if it were a named function.
greet('Pavan')
greet('Sahithi')
}
Functions As Input Arguments
Functions As Input Arguments
In V, functions are first-class types. This means that a function signature can be used as a parameter type for another function, allowing you to pass functional logic as an argument (a pattern known as a higher-order function). The type syntax for a function parameter matches its signature, such as f fn () string, representing a function f that takes no parameters and returns a string. You can pass named functions or anonymous functions inline directly.
This example declares multiple greeting helpers and a higher-order function greet that takes a greeting function and a name to construct a message.
Additional Context from Repository docs:
This example demonstrates the concepts of functions as input arguments.
module main
fn greet_morning() string {
return 'Good Morning'
}
fn greet_noon() string {
return 'Good Afternoon'
}
fn greet_evening() string {
return 'Good Evening'
}
// 'greet' is a higher-order function accepting 'f' (a function returning a string)
fn greet(f fn () string, name string) string {
// Call the passed function 'f' dynamically and interpolate the result
return '${f()}, ${name}!'
}
fn main() {
// Pass a named function 'greet_morning' directly
mut res := greet(greet_morning, 'Pavan')
println(res)
// Pass another named function 'greet_evening'
res = greet(greet_evening, 'Sahithi')
println(res)
// Pass an anonymous function inline directly as the argument
res = greet(fn () string {
return 'New year greetings to you'
}, 'Sahithi')
println(res)
}
Functions That Return Other Functions
Functions That Return Other Functions
Functions are reusable blocks of logic. This lesson on Functions That Return Other Functions explains functional syntax, arguments, returns, or functional capabilities in V.
Additional Context from Repository docs:
This example demonstrates the concepts of functions that return other functions.
module main
enum Operation {
add
sub
mul
}
fn adder(i int, j int) int {
return i + j
}
fn subtractor(i int, j int) int {
return i - j
}
fn multiplier(i int, j int) int {
return i * j
}
fn fetch(op Operation) fn (int, int) int {
return match op {
.add {
adder
}
.sub {
subtractor
}
.mul {
multiplier
}
}
}
fn main() {
i, j := 2, 5
mut f := fetch(.add) // return adder function
mut res := f(i, j) // calls adder(2, 5)
println('sum of ${i} and ${j}: ${res}')
f = fetch(.sub) // returns subtractor function
res = f(i, j) // calls subtractor(2, 5)
println('difference of ${i} and ${j}: ${res}')
f = fetch(.mul) // returns multipler function
res = f(i, j) // calls multiplier(2, 5)
println('product of ${i} and ${j}: ${res}')
}
Lambda Expressions
Lambda Expressions
V supports Lambda Expressions, which are lightweight, inline anonymous functions defined using the |variables| expression syntax.
Key architectural characteristics:
- Scope Restriction: Lambda expressions are not general-purpose functions; this syntax is only valid when passed directly as arguments to higher-order functions like
.sort(),.map(), and.filter(). - Implicit Returns: The result of the single expression on the right-hand side is automatically returned. No
returnkeyword is needed.
Step-by-Step Code Walkthrough:
- Sorting (
nums.sort(|a, b| b < a)):
The .sort() method accepts a comparator callback. The lambda expression defines parameters a and b, returning the comparison b < a to sort the array in descending order.
- Mapping (
nums.map(|x| x * 10)):
The .map() method transforms each array element. The lambda |x| x * 10 accepts the element x, multiplies it by 10, and produces the new mapped array.
- Filtering (
doubled.filter(|x| x > 20)):
The .filter() method checks a predicate. The lambda |x| x > 20 checks each element and retains only those returning true.
Additional Context from Repository docs:
This example demonstrates the concepts of lambda expressions.
module main
fn main() {
mut nums := [1, 3, 2, 5, 4]
// Sort descending using a lambda expression
nums.sort(|a, b| b < a)
println('Sorted: ${nums}') // [5, 4, 3, 2, 1]
// Map using lambda to multiply by 10
doubled := nums.map(|x| x * 10)
println('Doubled: ${doubled}') // [50, 40, 30, 20, 10]
// Filter using lambda to keep only elements > 20
filtered := doubled.filter(|x| x > 20)
println('Filtered (>20): ${filtered}') // [50, 40, 30]
}
Closures
Closures
V supports Closures, which are anonymous functions that "remember" and access variables from the parent scope in which they were created.
Unlike languages where variable capture is automatic and hidden, V implements explicit capture lists for safety and predictability:
- Explicit Capture Syntax: Captured variables must be declared inside square brackets
fn [captured_var] (args). - Pass-by-Value Capture (
[captured_var]): The variable's value is copied when the closure is created. It is read-only inside the closure body. - Pass-by-Reference Capture (
[mut captured_var]): Prepending the capture withmutpasses the variable by reference. Any changes made to the variable inside the closure modify the original variable in the parent scope, and vice-versa.
Step-by-Step Code Walkthrough:
new_counterFunction: Returns a closurefn () int.- State Preservation (
[mut count]): The closure captures the local variablecountfromnew_counterasmut. This allowscountto survive afternew_counterreturns and update its state across multiple invocation calls (e.g.counter()). - Value Capture (
[factor]): The closuremultipliercaptures thefactorvariable as read-only. Callingmultiplier(5)evaluates to5 * 10(50).
Additional Context from Repository docs:
This example demonstrates the concepts of closures.
module main
fn new_counter() fn () int {
mut count := 0
// The closure inherits `count` by reference (read-write) using explicit list `[mut count]`
return fn [mut count] () int {
count++
return count
}
}
fn main() {
counter := new_counter()
println(counter()) // 1
println(counter()) // 2
println(counter()) // 3
// An immutable capture closure
factor := 10
multiplier := fn [factor] (x int) int {
return x * factor
}
println(multiplier(5)) // 50
}
Chapter 7 Structs (Custom Types)
Quick Access
Below is an index of all code examples in this chapter. You can use these links to jump directly to any specific code example:
Struct Basics & Fields
- Defining Struct
- Initialize Struct Example 1
- Initialize Struct Example 2
- Access Struct Fields
- Heap Structs
- Updating Immutable Struct Variable Throws Error
- Updating Mutable Fields Of Struct
- Updating Immutable Fields Throws Error
- Updating Struct With Unspecified Fields Are Zeroed
- Struct With Multiple Fields
- Grouping Struct Fields Based On Access Modifiers
- Required Fields Example 01
- Required Fields Example 02
- Struct Fields With Default Values
- Methods For Struct
- Mutable Methods
- Printing Custom Types
- Adding Struct As Struct Field
- Updating Fields Of Type Struct
- Struct As Trailing Literal Arguments To Function
- Anonymous Structs
- Static Type Methods
- noinit Structs
- Unions
Structs are user-defined data structures that allow you to group related fields together. This chapter explains how to define structs, set default values, make fields required, attach methods to structs, and embed structs inside other structures.
Struct Basics & Fields
Defining Struct
Defining Struct
A struct is a user-defined custom type that groups related variables (called fields) together. Structs are fundamental to V's object-oriented programming model. By default, struct fields are private and immutable. V provides access modifiers like mut:, pub:, and pub mut: to control field access and mutability.
These examples demonstrate defining structs, updating fields, required fields, default values, and struct methods.
Additional Context from Repository docs:
This example demonstrates the concepts of defining struct.
struct Note {
id int
message string
}
fn main() {
note := Note{
id: 1
message: 'A simple struct demo'
}
println(note)
}
Initialize Struct Example 1
Initialize Struct Example 1
A struct is a user-defined custom type that groups related variables (called fields) together. Structs are fundamental to V's object-oriented programming model. By default, struct fields are private and immutable. V provides access modifiers like mut:, pub:, and pub mut: to control field access and mutability.
These examples demonstrate defining structs, updating fields, required fields, default values, and struct methods.
Additional Context from Repository docs:
This example demonstrates the concepts of initialize struct example 1.
struct Note {
id int
message string
}
fn main() {
note := Note{1, 'A simple struct demo'}
println('ID: ${note.id}')
println('Message: ${note.message}')
}
Initialize Struct Example 2
Initialize Struct Example 2
A struct is a user-defined custom type that groups related variables (called fields) together. Structs are fundamental to V's object-oriented programming model. By default, struct fields are private and immutable. V provides access modifiers like mut:, pub:, and pub mut: to control field access and mutability.
These examples demonstrate defining structs, updating fields, required fields, default values, and struct methods.
Additional Context from Repository docs:
This example demonstrates the concepts of initialize struct example 2.
struct Note {
id int
message string
}
fn main() {
note := Note{
message: 'A named-field struct demo'
id: 2
}
println(typeof(note).name)
println(note)
}
Access Struct Fields
Access Struct Fields
A struct is a user-defined custom type that groups related variables (called fields) together. Structs are fundamental to V's object-oriented programming model. By default, struct fields are private and immutable. V provides access modifiers like mut:, pub:, and pub mut: to control field access and mutability.
These examples demonstrate defining structs, updating fields, required fields, default values, and struct methods.
Additional Context from Repository docs:
This example demonstrates the concepts of access struct fields.
struct Note {
id int
message string
}
fn main() {
n := Note{1, 'a simple struct demo'}
println(n.message)
}
Heap Structs
Heap Structs
By default, V allocates struct instances on the stack, which is fast and manages memory automatically when variables go out of scope. However, for large structures or instances that must survive beyond the current function scope, you should allocate them on the heap. In V, you allocate a struct on the heap by prepending the initialization literal with the reference operator & (e.g., &MyStruct{}). The type of a heap-allocated struct is a pointer type, represented as &MyStruct (read-only reference).
This example demonstrates declaring a heap-allocated struct instance and printing its type name.
Additional Context from Repository docs:
This example demonstrates the concepts of heap structs.
struct Note {
id int
message string
}
fn main() {
// Prepending & allocates the Note instance on the heap rather than the stack.
// 'n1' holds a reference (pointer) to the heap-allocated note.
n1 := &Note{1, 'this note will be allocated on heap'}
// Prints the type of n1, which is &Note (pointer/reference type)
println(typeof(n1).name) // &Note
}
Updating Immutable Struct Variable Throws Error
Updating Immutable Struct Variable Throws Error
A struct is a user-defined custom type that groups related variables (called fields) together. Structs are fundamental to V's object-oriented programming model. By default, struct fields are private and immutable. V provides access modifiers like mut:, pub:, and pub mut: to control field access and mutability.
These examples demonstrate defining structs, updating fields, required fields, default values, and struct methods.
Additional Context from Repository docs:
This example demonstrates the concepts of updating immutable struct variable throws error.
module main
struct Note {
id int
mut:
message string
}
fn main() {
n := Note{1, 'a simple struct demo'}
println(n)
n.message = 'a simple struct updated' // throws error
}
Updating Mutable Fields Of Struct
Updating Mutable Fields Of Struct
In V, struct fields are read-only (immutable) by default. To make specific fields mutable, you must group them under the mut: access modifier within the struct definition. However, defining a field as mutable only makes it eligible for mutation; to actually modify the field on a struct instance at runtime, the instance itself must be declared as a mutable variable using the mut keyword (e.g., mut my_instance := MyStruct{}). If the instance is declared as immutable, the compiler will reject any attempt to modify its fields even if they are defined under mut:.
This example defines a Note struct with a mutable message field, initializes a mutable instance, and updates its value.
Additional Context from Repository docs:
This example demonstrates the concepts of updating mutable fields of struct.
module main
struct Note {
// 'id' is immutable by default and cannot be updated.
id int
mut:
// 'message' is declared mutable, allowing updates if the struct instance is mutable.
message string
}
fn main() {
// Declare the struct instance 'n' as mutable using the 'mut' keyword
mut n := Note{1, 'a simple struct demo'}
println('before update')
println(n)
// Modify the mutable field message
n.message = 'a simple struct updated'
println('after update')
println(n)
}
Updating Immutable Fields Throws Error
Updating Immutable Fields Throws Error
A struct is a user-defined custom type that groups related variables (called fields) together. Structs are fundamental to V's object-oriented programming model. By default, struct fields are private and immutable. V provides access modifiers like mut:, pub:, and pub mut: to control field access and mutability.
These examples demonstrate defining structs, updating fields, required fields, default values, and struct methods.
Additional Context from Repository docs:
This example demonstrates the concepts of updating immutable fields throws error.
module main
struct Note {
id int
mut:
message string
}
fn main() {
mut j := Note{1, 'a simple struct demo'}
j.id = 2 // throws error
}
Updating Struct With Unspecified Fields Are Zeroed
Updating Struct With Unspecified Fields Are Zeroed
A struct is a user-defined custom type that groups related variables (called fields) together. Structs are fundamental to V's object-oriented programming model. By default, struct fields are private and immutable. V provides access modifiers like mut:, pub:, and pub mut: to control field access and mutability.
These examples demonstrate defining structs, updating fields, required fields, default values, and struct methods.
Additional Context from Repository docs:
This example demonstrates the concepts of updating struct with unspecified fields are zeroed.
module main
struct Note {
id int
mut:
message string
}
fn main() {
// declare
mut n := Note{}
// populate
n = Note{
id: 1
message: 'updating struct fields demo'
}
println(n)
// unspecified fields zeroed by default
// id being type of int, will become 0 here
println('unspecified id zeroed during short struct type initialization')
n = Note{
message: 'updating struct fields demo 2'
}
println(n)
}
Struct Update Syntax
Struct Update Syntax
V provides a convenient syntax to create and return a modified copy of a struct instance by spreading the original fields using ... and overwriting specific ones:
module main
struct User {
name string
age int
is_registered bool
}
fn register(u User) User {
// Returns a modified copy using the struct update syntax
return User{
...u
is_registered: true
}
}
fn main() {
println('=== Struct Update Syntax ===')
user1 := User{
name: 'Ada'
age: 36
}
user2 := register(user1)
println('user1: ${user1}') // User{name: 'Ada', age: 36, is_registered: false}
println('user2: ${user2}') // User{name: 'Ada', age: 36, is_registered: true}
assert user1.is_registered == false
assert user2.is_registered == true
assert user2.name == 'Ada'
assert user2.age == 36
}
Struct With Multiple Fields
Struct With Multiple Fields
A struct is a user-defined custom type that groups related variables (called fields) together. Structs are fundamental to V's object-oriented programming model. By default, struct fields are private and immutable. V provides access modifiers like mut:, pub:, and pub mut: to control field access and mutability.
These examples demonstrate defining structs, updating fields, required fields, default values, and struct methods.
Additional Context from Repository docs:
This example demonstrates the concepts of struct with multiple fields.
struct Note {
id int
mut:
message string
status bool
}
fn main() {
}
Grouping Struct Fields Based On Access Modifiers
Grouping Struct Fields Based On Access Modifiers
A struct is a user-defined custom type that groups related variables (called fields) together. Structs are fundamental to V's object-oriented programming model. By default, struct fields are private and immutable. V provides access modifiers like mut:, pub:, and pub mut: to control field access and mutability.
These examples demonstrate defining structs, updating fields, required fields, default values, and struct methods.
Additional Context from Repository docs:
This example demonstrates the concepts of grouping struct fields based on access modifiers.
pub struct Note {
pub:
id int
pub mut:
message string
status bool
}
fn main() {
}
Required Fields Example 01
Required Fields Example 01
A struct is a user-defined custom type that groups related variables (called fields) together. Structs are fundamental to V's object-oriented programming model. By default, struct fields are private and immutable. V provides access modifiers like mut:, pub:, and pub mut: to control field access and mutability.
These examples demonstrate defining structs, updating fields, required fields, default values, and struct methods.
Additional Context from Repository docs:
This example demonstrates the concepts of required fields example 01.
pub struct Note {
pub:
id int
pub mut:
message string @[required]
status bool
}
fn main() {
_ := Note{
id: 1
status: false
}
}
// throws error
Required Fields Example 02
Required Fields Example 02
A struct is a user-defined custom type that groups related variables (called fields) together. Structs are fundamental to V's object-oriented programming model. By default, struct fields are private and immutable. V provides access modifiers like mut:, pub:, and pub mut: to control field access and mutability.
These examples demonstrate defining structs, updating fields, required fields, default values, and struct methods.
Additional Context from Repository docs:
This example demonstrates the concepts of required fields example 02.
module main
pub struct Note {
pub:
id int
pub mut:
message string @[required]
status bool
}
fn main() {
n := Note{
id: 1
message: 'a simple struct demo'
status: false
}
println(n)
}
Struct Fields With Default Values
Struct Fields With Default Values
A struct is a user-defined custom type that groups related variables (called fields) together. Structs are fundamental to V's object-oriented programming model. By default, struct fields are private and immutable. V provides access modifiers like mut:, pub:, and pub mut: to control field access and mutability.
These examples demonstrate defining structs, updating fields, required fields, default values, and struct methods.
Additional Context from Repository docs:
This example demonstrates the concepts of struct fields with default values.
import time
pub struct Note {
pub:
id int
created time.Time = time.now()
pub mut:
message string @[required]
status bool
due time.Time = time.now().add_days(1)
}
fn main() {
n := Note{
id: 1
message: 'order groceries'
}
println(n)
}
Methods For Struct
Methods For Struct
A struct is a user-defined custom type that groups related variables (called fields) together. Structs are fundamental to V's object-oriented programming model. By default, struct fields are private and immutable. V provides access modifiers like mut:, pub:, and pub mut: to control field access and mutability.
This lesson demonstrates defining structs, updating fields, required fields, default values, and value receiver methods.
Additional Context from Repository docs:
This example demonstrates the concepts of methods for struct.
module main
import time
// 1. Define a Struct.
// Structs are defined with the `struct` keyword. By default, structs are private
// (only accessible within the current module) and all fields are immutable.
// The `pub` keyword makes the struct visible to other modules.
pub struct Note {
// 2. Struct Access Modifiers:
// - `pub:` makes the fields readable from outside the module, but still immutable.
pub:
id int
// Fields can have default values assigned at declaration.
created time.Time = time.now()
// - `pub mut:` makes the fields readable and writable from outside the module.
pub mut:
// Attributes can be attached to struct fields.
// `@[required]` specifies that this field must be explicitly provided when instantiating.
message string @[required]
status bool
due time.Time = time.now().add_days(1)
}
// 3. Define a Method (Value Receiver).
// In V, a method is a function with a receiver argument.
// The receiver is specified in parentheses before the function name: `(n Note)`.
// This is a "value receiver" method, meaning it receives a copy of the struct instance.
// It cannot modify fields on the original struct instance.
pub fn (n Note) is_empty_message() bool {
return n.message.len < 1
}
fn main() {
// 4. Instantiate a Struct.
// We use the struct name and curly braces, listing field initializations.
// Because `message` is marked `@[required]`, we must specify it.
// The variable `n` is marked `mut` because we might want to update its `pub mut` fields.
mut n := Note{
id: 1
message: ''
}
// 5. Invoke Struct Methods.
// Methods are called on struct instances using the dot operator.
if n.is_empty_message() {
println('message is empty')
} else {
println('message not empty')
}
}
Mutable Methods
Mutable Methods
By default, struct methods in V receive a read-only copy of the struct instance (value receiver). If a method needs to modify any fields of the struct, it must declare a mutable receiver using the mut keyword, e.g., fn (mut n Note) mark_as_completed().
Additionally, the struct instance variable must be declared with mut at the call site to allow mutable method invocations.
Additional Context from Repository docs:
This example demonstrates the concepts of mutable methods for struct.
module main
import time
pub struct Note {
pub:
id int
created time.Time = time.now()
pub mut:
message string @[required]
status bool
due time.Time = time.now().add_days(1)
}
// 1. Define a Mutable Struct Method.
// To modify fields of a struct inside a method, the receiver must be marked mutable: `(mut n Note)`.
// Under the hood, this passes a pointer/mutable reference, allowing the method to update
// the original struct instance fields directly.
pub fn (mut n Note) mark_as_completed() {
n.status = true
println('Note [ID: ${n.id}] marked as completed.')
}
// 2. Define another Mutable Method to update the message.
pub fn (mut n Note) update_message(new_msg string) {
if new_msg.len > 0 {
n.message = new_msg
println('Note [ID: ${n.id}] message updated to: "${new_msg}"')
}
}
fn main() {
// 3. Instantiate a mutable struct instance.
// To call mutable methods on a struct, the variable MUST be declared as mutable (`mut`).
// If `n` was immutable, calling `n.mark_as_completed()` would result in a compilation error.
mut n := Note{
id: 42
message: 'Learn V programming'
status: false
}
println('Initial state - Message: "${n.message}", Completed: ${n.status}')
// 4. Call the mutable methods.
n.update_message('Master V programming and C interop!')
n.mark_as_completed()
// 5. Verify the updates.
println('Final state - Message: "${n.message}", Completed: ${n.status}')
assert n.status == true
}
Printing Custom Types
Printing Custom Types
By default, passing a struct instance to functions like println will print its fields in a default format (e.g., Color{r: 255, g: 0, b: 0}). However, if you want a custom, human-readable string representation of your struct type, you can define a str() string method on it. V's runtime automatically checks for this method when converting values to strings or when outputting using printing functions.
This example illustrates defining a str() string method on a custom Color struct to format it as {r, g, b}.
Additional Context from Repository docs:
This example demonstrates the concepts of printing custom types.
module main
struct Color {
r int
g int
b int
}
// By defining a method named str() returning a string, we customize how Color is printed.
pub fn (c Color) str() string {
return '{${c.r}, ${c.g}, ${c.b}}'
}
fn main() {
red := Color{
r: 255
g: 0
b: 0
}
// println automatically detects and calls the custom .str() method on Color
println(red)
}
Adding Struct As Struct Field
Adding Struct As Struct Field
A struct is a user-defined custom type that groups related variables (called fields) together. Structs are fundamental to V's object-oriented programming model. By default, struct fields are private and immutable. V provides access modifiers like mut:, pub:, and pub mut: to control field access and mutability.
These examples demonstrate defining structs, updating fields, required fields, default values, and struct methods.
Additional Context from Repository docs:
This example demonstrates the concepts of adding struct as struct field.
import time
// NoteTimeInfo is a struct to store time info of Note
pub struct NoteTimeInfo {
pub:
created time.Time = time.now()
pub mut:
due time.Time = time.now().add_days(1)
}
// Note is a struct with struct NoteTimeInfo as a field, along with other fields
pub struct Note {
NoteTimeInfo // Struct as another struct field
pub:
id int
pub mut:
message string @[required]
status bool
}
fn main() {
n := Note{
id: 1
message: 'adding struct as struct field demo'
}
println('Due date: ${n.due}')
println(n)
}
Updating Fields Of Type Struct
Updating Fields Of Type Struct
A struct is a user-defined custom type that groups related variables (called fields) together. Structs are fundamental to V's object-oriented programming model. By default, struct fields are private and immutable. V provides access modifiers like mut:, pub:, and pub mut: to control field access and mutability.
These examples demonstrate defining structs, updating fields, required fields, default values, and struct methods.
Additional Context from Repository docs:
This example demonstrates the concepts of updating fields of type struct.
module main
import time
// NoteTimeInfo is a struct to store time info of Note
pub struct NoteTimeInfo {
pub:
created time.Time = time.now()
pub mut:
due time.Time = time.now().add_days(1)
}
// Note is a struct with struct NoteTimeInfo as a field, along with other fields
pub struct Note {
NoteTimeInfo
pub:
id int
pub mut:
message string @[required]
status bool
}
fn main() {
mut n := Note{
id: 1
message: 'adding struct as struct field demo'
}
println('Due date: ${n.due}')
// approach 1: implicit access of struct fields of fields of type struct
n.due = n.due.add_days(2)
println('Due date after update: ${n.due}')
// approach 2: explicitly specifying the field of type struct and its fields
n.NoteTimeInfo.due = n.NoteTimeInfo.due.add_days(2)
println('Due date updated second time: ${n.due}')
println(n)
}
Struct As Trailing Literal Arguments To Function
Struct As Trailing Literal Arguments To Function
A struct is a user-defined custom type that groups related variables (called fields) together. Structs are fundamental to V's object-oriented programming model. By default, struct fields are private and immutable. V provides access modifiers like mut:, pub:, and pub mut: to control field access and mutability.
These examples demonstrate defining structs, updating fields, required fields, default values, and struct methods.
Additional Context from Repository docs:
This example demonstrates the concepts of struct as trailing literal arguments to function.
module main
import time
// NoteTimeInfo is a struct to store time info of Note
pub struct NoteTimeInfo {
pub:
created time.Time = time.now()
pub mut:
due time.Time = time.now().add_days(1)
}
// Note is a struct with embedding struct NoteTimeInfo along with other fields
pub struct Note {
NoteTimeInfo
pub:
id int
pub mut:
message string @[required]
status bool
}
fn new_grocery_note(n Note) &Note {
return &Note{
id: n.id
message: 'Buy Groceries: ' + n.message
}
}
fn extend_due_by_a_day(n Note) &Note {
return &Note{
NoteTimeInfo: NoteTimeInfo{
due: n.due.add_days(1)
}
id: n.id
message: n.message
}
}
fn main() {
g := new_grocery_note(Note{ id: 1, message: 'Milk' })
println('${g.message} is due by ${g.due}')
n := extend_due_by_a_day(g)
println('After extending due date by a day')
println('${n.message} is due by ${n.due}')
}
Trailing Struct Literal Arguments
Trailing Struct Literal Arguments
V does not support default function arguments or named arguments. Instead, you can use trailing struct literal arguments. By tagging a configuration struct with the @[params] attribute, V allows you to omit both the struct name and the curly braces when calling the function if it is the final argument.
module main
@[params]
struct ButtonConfig {
text string
is_disabled bool
width int = 70
height int = 20
}
struct Button {
text string
width int
height int
}
fn new_button(c ButtonConfig) &Button {
return &Button{
width: c.width
height: c.height
text: c.text
}
}
fn main() {
println('=== Trailing Struct Literal Arguments ===')
// Omitting both the struct name and braces
button := new_button(text: 'Click me', width: 100)
println('button: width=${button.width}, height=${button.height}, text="${button.text}"')
assert button.height == 20
assert button.width == 100
assert button.text == 'Click me'
}
Anonymous Structs
Anonymous Structs
V supports Anonymous Structs which are inline struct declarations without separate struct names. They are useful for one-off structures like local nested objects.
Step-by-Step Code Walkthrough:
- Inline Sub-Struct Declaration:
In the Book struct definition, the author field is declared as an anonymous struct type with fields name string and age int. No named struct like Author is required.
- Inline Struct Initialization:
Inside main(), we instantiate Book. The nested author field is initialized directly using struct { name: 'Samantha Black', age: 24 }, matching the field structure.
- Field Access:
Nested fields are accessed sequentially using dot notation: book.author.name and book.author.age.
Additional Context from Repository docs:
This example demonstrates the concepts of anonymous structs.
module main
import json
struct Book {
title string
mut:
author struct {
name string
mut:
age int
}
}
fn main() {
mut book := Book{
title: 'The V Programming Language'
author: struct {
name: 'Samantha Black'
age: 24
}
}
book.author.age = 25
println('${book.title} by ${book.author.name} (${book.author.age})')
println(json.encode(book))
}
Static Type Methods
Static Type Methods
V supports Static Type Methods (e.g. User.new()). These are defined on a struct via fn [StructName].[methodName] and allow organizing all constructor/factory functions related to a struct. V does not have traditional class constructors; static type methods are standard functions namespace-associated with the struct.
Step-by-Step Code Walkthrough:
- Static Method Definition:
fn User.new(name string, age int) User declares a static method named new associated with the User struct namespace. It returns a new User instance.
- Factory Organization:
The static method User.default_user() calls User.new('Guest', 18) to construct a user with default values, acting as a clean factory builder.
- Invocation Syntax:
Inside main(), static methods are invoked using the struct name prefix: User.new(...) and User.default_user(). This prevents global namespace pollution and groups constructor-like logic cleanly.
Additional Context from Repository docs:
This example demonstrates the concepts of static type methods.
module main
struct User {
name string
age int
}
// Defining a static type method on User
fn User.new(name string, age int) User {
return User{
name: name
age: age
}
}
// Another static method
fn User.default_user() User {
return User.new('Guest', 18)
}
fn main() {
// Call static type methods using StructName.method_name()
user1 := User.new('Bob', 25)
user2 := User.default_user()
println('User 1: ${user1.name}, Age: ${user1.age}')
println('User 2: ${user2.name}, Age: ${user2.age}')
}
noinit Structs
noinit Structs
V supports [noinit] structs which are structs that cannot be initialized directly outside of their declaring module. This forces client code to use factory constructor functions to instantiate the struct, enabling strict initialization checks and API boundaries.
Step-by-Step Code Walkthrough:
- Declaring [noinit]:
In the noinit_config module (noinit_config.v), the Config struct is marked with the @[noinit] attribute. This blocks external modules from directly initializing it using literals like noinit_config.Config{ ... }.
- Exposing a Constructor:
We provide a public factory function pub fn new_config(port int, host string) Config inside the noinit_config module, which is authorized to initialize and return the struct.
- Compiler Enforcement:
In the main module (noinit_structs.v), creating noinit_config.new_config(...) compiles and runs successfully. Attempting to directly write noinit_config.Config{port: 8080} would cause a compilation error.
Additional Context from Repository docs:
This example demonstrates the concepts of noinit structs.
module noinit_config
@[noinit]
pub struct Config {
pub:
port int
host string
}
// Public constructor function to allow initialization from outside
pub fn new_config(port int, host string) Config {
return Config{
port: port
host: host
}
}
import noinit_config
fn main() {
// This works because it uses the constructor function
cfg := noinit_config.new_config(8080, 'localhost')
println('Config port: ${cfg.port}, host: ${cfg.host}')
// This would fail compilation because noinit_config.Config is marked [noinit]:
// cfg2 := noinit_config.Config{ port: 8080, host: 'localhost' }
}
Unions
Unions
A Union is a special type of struct that allows storing different data types in the same memory location. The largest member defines the size of the union. All members share the same memory location, meaning modifying one member automatically modifies the shared representation of the others. Union field access is considered memory-unsafe and must always be performed inside unsafe {} blocks.
Step-by-Step Code Walkthrough:
- Union Declaration & Mutability:
union Data declares two fields: f f64 (8 bytes) and i int (4 bytes). Because they are in a union, they share the same starting memory address, and the total size of Data is 8 bytes. By default in V, union fields are immutable; we must place them under a mut: block inside the union declaration to allow their values to be reassigned.
- Memory Corruption Demonstration:
We initialize the union with an integer i: 10.
Inside unsafe { ... }, when we assign d.f = 5.5, the float value overwrites the shared memory. Reading d.i subsequently returns a garbled integer representing the binary layout of the float 5.5, demonstrating the shared storage layout.
- Safety Restriction:
Accessing any field of a union (d.i or d.f) is blocked by the compiler unless wrapped in an unsafe block, protecting developers from accidental memory misinterpretation.
Additional Context from Repository docs:
This example demonstrates the concepts of unions.
module main
// Define a union sharing the same memory location, marked mutable
union Data {
mut:
f f64
i int
}
fn main() {
mut d := Data{
i: 10
}
// Accessing union members must be performed in an unsafe block
unsafe {
println('Union int value: ${d.i}')
// Modifying one member automatically modifies the other since they share memory
d.f = 5.5
println('Union float value: ${d.f}')
println('Union int value after float update: ${d.i} (shared memory representation)')
}
}
Structs with Reference Fields
Structs with Reference Fields
Structs can store reference fields/pointers (prefixed with &). Reference fields must be initialized to a valid address or explicitly auto-initialized using = unsafe { nil } (use with caution, as nil pointer dereferences will crash/panic).
module main
struct Node {
a &Node
b &Node = unsafe { nil } // Auto-initialized to nil
}
fn main() {
println('=== Struct Reference Fields ===')
foo := Node{
a: unsafe { nil }
}
bar := Node{
a: &foo
}
println('foo: ${foo}')
println('bar: ${bar}')
assert bar.a == &foo
}
Chapter 8 Error Handling
Quick Access
Below is an index of all code examples in this chapter. You can use these links to jump directly to any specific code example:
Option & Result Types
V has no exceptions. Instead, it handles errors using Option and Result types, which are checked at compile time. This chapter teaches you how to write robust, error-free programs using V's clean error handling syntax.
Option & Result Types
Error Handling
In many programming languages, errors are handled using exceptions (with try, catch, and throw blocks). Exception blocks can make code hard to read and trace because control flow can jump unpredictably.
V takes a different approach. V does not have exceptions. Instead, V handles errors explicitly using two main concepts:
- Option Types (
?T): Used when a value might simply be missing (like searching for an item that isn't in a list). - Result Types (
!T): Used when an operation might actually fail with a specific error (like division by zero or a database timeout).
By forcing you to handle these outcomes explicitly, V makes your code safer and easier to debug.
module main
// ==========================================
// Define Custom Error Types
// ==========================================
// CustomError embeds the builtin Error struct to implement the IError interface.
struct CustomError {
Error // Required: provides default implementations of msg() and code()
message string
code int
}
// Overwrite the msg() method for CustomError
fn (err CustomError) msg() string {
return err.message
}
// Overwrite the code() method for CustomError
fn (err CustomError) code() int {
return err.code
}
// DatabaseError represents another custom error type.
struct DatabaseError {
Error
query string
}
fn (err DatabaseError) msg() string {
return 'Database error executing query: "${err.query}"'
}
// ==========================================
// 1. Option Types (?T)
// Options represent either a value of type T or nothing (none).
// ==========================================
// find_item returns a string if found, or none if not.
fn find_item(id int) ?string {
if id == 42 {
return 'V programming book'
}
return none // return absence of value
}
// find_item_wrapper demonstrates Option propagation with the `?` suffix.
fn find_item_wrapper(id int) ?string {
// If find_item returns none, the execution stops here and propagates none up.
item := find_item(id)?
return 'Found: ' + item
}
// ==========================================
// 2. Result Types (!T)
// Results represent either a value of type T or an IError.
// ==========================================
// divide performs float division but returns an error for division by zero.
fn divide(a f64, b f64) !f64 {
if b == 0.0 {
return error('division by zero') // Return a standard error
}
return a / b
}
// fetch_data returns a string or a CustomError.
fn fetch_data(success bool) !string {
if !success {
return CustomError{
message: 'Connection timed out'
code: 504
}
}
return 'Raw database records'
}
// query_db returns a string or a DatabaseError.
fn query_db(query string, success bool) !string {
if !success {
return DatabaseError{
query: query
}
}
return 'Query success'
}
// calculate_and_format demonstrates Result propagation using the `!` operator.
fn calculate_and_format(a f64, b f64) !string {
// The `!` suffix propagates the error to the caller if divide fails.
res := divide(a, b)!
return 'Result is ${res:.2f}'
}
// ==========================================
// 3. Unrecoverable Errors (Panics)
// ==========================================
fn force_panic() {
println('Simulating a critical failure...')
panic('Fatal error: Out of memory or system crash.')
}
fn main() {
println('=== 1. Option Types (?T) ===')
// Option Handling: Option unwrapping using `or` block
item_1 := find_item(42) or { 'Default Item' }
println('Item 1 (with 42): ${item_1}')
item_2 := find_item(99) or { 'Default Item' }
println('Item 2 (with 99): ${item_2}')
// Option Handling: Option unwrapping with variable binding using `if-let`
if item := find_item(42) {
println('If-let match: Found "${item}"')
} else {
println('If-let match: Item not found')
}
if item := find_item(99) {
println('If-let match: Found "${item}"')
} else {
println('If-let match: Item not found (none)')
}
// Option Propagation Check
wrapped_item := find_item_wrapper(99) or { 'None propagated successfully' }
println('Propagation check: ${wrapped_item}\n')
println('=== 2. Result Types (!T) ===')
// Result Handling: Standard error message extraction via the `err` variable inside `or` block
calc_success := calculate_and_format(10.0, 2.0) or { 'Error: ${err}' }
println('Calc success: ${calc_success}')
calc_fail := calculate_and_format(10.0, 0.0) or { 'Error: ${err}' }
println('Calc failure: ${calc_fail}')
println('\n=== 3. Custom Error Matching & Type Casting ===')
// We can inspect the error type dynamically using the `is` check inside the `or` block.
// Since fetch_data(false) returns a Result type (!string), the `or` block must either:
// 1. Terminate control flow (e.g. using return, panic, exit)
// 2. Evaluate to a fallback string value.
// We use `''` (empty string) here as the fallback value to satisfy this type requirement.
fetch_data(false) or {
if err is CustomError {
// Inside this block, `err` is smart-cast to CustomError automatically,
// allowing direct access to custom fields like `code`.
println('Caught CustomError! Message: "${err.msg()}", Code: ${err.code}')
} else {
println('Caught generic error: ${err.msg()}')
}
'' // Fallback empty string returned to satisfy the !string return type of the or block
}
// Similarly, query_db returns !string, so its or block must also evaluate to a string.
query_db('SELECT * FROM users', false) or {
if err is DatabaseError {
// Smart-cast to DatabaseError, accessing the `query` field
println('Caught DatabaseError!')
println('Query attempted: "${err.query}"')
println('Error message: "${err.msg()}"')
} else {
println('Caught generic error: ${err.msg()}')
}
'' // Fallback empty string returned to satisfy the !string return type of the or block
}
println('\n=== 4. Panic (Unrecoverable Error) ===')
// We wrap panic execution or run it last since it terminates the process.
// You can uncomment the line below to test panic termination:
// force_panic()
println('To run a panic, uncomment force_panic() in main.')
}
Deep Dive Explanation
1. Option (`?T`) vs. Result (`!T`) Types
V enforces safety by separating missing data from actual runtime failures at the type system level:
- Option Type (
?T): Declares that a variable or function return value can either hold a value of typeTornone(denoting absence). Use options for operations like lookups or querying optional attributes. - Result Type (
!T): Declares that an operation returns either a value of typeTor an error that implements theIErrorinterface. Use results for operations that can fail due to external factors (e.g., IO, math division, DB connection).
2. Unwrapping with the `or` Block
When invoking a function that returns an Option or a Result, V requires you to explicitly unwrap it using an or block:
value := maybe_value() or { fallback_value }
The or block acts as a recovery scope and must adhere to one of the following two rules:
- Provide a Fallback Value: It must evaluate to an expression matching type
T. - Halt or Divert Control Flow: It must use keywords like
return,panic(),exit(),break, orcontinueto exit the current scope.
For functions returning a Result type, V automatically exposes an implicit variable named err (of type IError) inside the or block. You can call err.msg() or err.code() to inspect the failure:
result := divide(10.0, 0.0) or {
println('Math error: ' + err.msg())
0.0
}
3. Error and Option Propagation
Instead of handling errors immediately with an or block, you can bubble them up to the caller using propagation suffixes:
- Use the
?suffix to propagatenonefrom an optional-returning function:
item := find_item(id)? // Returns none to the caller if find_item fails
- Use the
!suffix to propagate errors from a result-returning function:
res := divide(a, b)! // Propagates the IError up to the caller if b == 0.0
Note: A function can only use the propagation suffix if its own return type matches (i.e., returns ?U or !U respectively).
4. Custom Error Structs and the `IError` Interface
To build custom error types, define a struct and embed the builtin Error struct. Embedding Error ensures your custom struct implements the IError interface:
struct CustomError {
Error // Embed standard Error fields and methods
message string
code int
}
You can override the msg() and code() methods to define how the error is printed and what status code it carries.
5. Type Assertions and Smart Casting with `is`
When handling generic IError values inside an or block, you can query their concrete types using the is keyword:
fetch_data(false) or {
if err is CustomError {
// V smart-casts 'err' to CustomError here
println('Custom code: ${err.code}')
}
''
}
If the type check matches, V automatically smart-casts err inside that block, allowing you to access custom fields (like code or query) without explicit casting.
Chapter 9 Organizing Code with Modules
Quick Access
Below is an index of all code examples in this chapter. You can use these links to jump directly to any specific code example:
Modules & Project Structure
- Creating a Simple V Project - Main (modulebasics.v)
- Creating a Module - Helper (file1.v)
- Creating a Module - Main (modulebasics.v)
- Importing a Module - Helper (file1.v)
- Importing a Module - Main (modulebasics.v)
- Accessing Module Members - Helper (file1.v)
- Accessing Module Members - Main (modulebasics.v)
- Multiple Files (After Refactoring) - Helper 1 (file1.v)
- Multiple Files (After Refactoring) - Helper 2 (file2.v)
- Multiple Files (After Refactoring) - Main (modulebasics.v)
- Multiple Files (Before Refactoring) - Helper 1 (file1.v)
- Multiple Files (Before Refactoring) - Helper 2 (file2.v)
- Multiple Files (Before Refactoring) - Main (modulebasics.v)
- Member Scope (After Refactoring) - Helper 1 (file1.v)
- Member Scope (After Refactoring) - Helper 2 (file2.v)
- Member Scope (After Refactoring) - Main (modulebasics.v)
- Member Scope (Before Refactoring) - Helper 1 (file1.v)
- Member Scope (Before Refactoring) - Helper 2 (file2.v)
- Member Scope (Before Refactoring) - Main (modulebasics.v)
- Cyclic Imports - Module 1 Helper (file1.v)
- Cyclic Imports - Module 2 Helper (file1.v)
- Cyclic Imports - Main (modulebasics.v)
- Module Init & Cleanup Functions - Config (config.v)
- Module Init & Cleanup Functions - Helper (file1.v)
- Module Init & Cleanup Functions - Main (modulebasics.v)
- Accessing Module Constants - Helper (file1.v)
- Accessing Module Constants - Main (modulebasics.v)
- Accessing Module Structs - Helper (file1.v)
- Accessing Module Structs - Main (modulebasics.v)
Installing External Packages
- How to Install Packages with vpm
- Common vpm Commands
- Importing and Using External Packages
- Redis Console Demo
- Redis Console Demo - Helper (redis_helper.v)
- Redis Namespaced Demo - Helper (redis_helper.v)
- Redis Namespaced Demo
- Redis Webview Demo
- Webview Demo
- Packaging Webview as macOS Binaries
Modules help organize larger codebases. In this chapter, you will learn how to create modules, import them, manage member visibility using pub, and understand module initialization lifecycle.
Modules & Project Structure
Creating a Simple V Project - Main (modulebasics.v)
Creating a Simple V Project
Think of a module as a small toolbox. The main module is the entry point of your program, while other modules can hold reusable functions and types.
This first example is intentionally simple: it shows the structure of a single-file V program before we introduce imports and shared modules.
module main
fn main() {
println('Hello World!')
}
Creating a Module - Helper (file1.v)
A Reusable Helper Module
A module can hold functions that other parts of your program can reuse. In this example, the helper module mod1 exposes a public function called greet.
module mod1
pub fn hello() {
println('Hello from mod1!')
}
Creating a Module - Main (modulebasics.v)
Module Main Entry
This file acts as the application entry point. It imports the helper module and calls one of its public functions.
module main
fn main() {
println('Hello World!')
}
Importing a Module - Helper (file1.v)
Imported Module Helper
Importing a module gives your program access to its public members. The module name becomes the namespace you use when calling those functions.
module mod1
pub fn hello() {
println('Hello from mod1!')
}
Importing a Module - Main (modulebasics.v)
Imported Module Main
The main program can now use the imported module without copying its code into the entry file.
module main
import mod1
fn main() {
println('Hello World!')
}
Accessing Module Members - Helper (file1.v)
Public vs Private Members
Not everything in a module should be accessible from outside. In V, pub makes a function available to other modules, while private functions stay inside the module.
module mod1
pub fn hello() {
println('Hello from mod1!')
}
Accessing Module Members - Main (modulebasics.v)
Member Visibility Main
From the main program, you can call the public function, but private helpers remain hidden.
module main
import mod1
fn main() {
mod1.hello()
println('Hello World!')
}
Multiple Files (After Refactoring) - Helper 1 (file1.v)
Multiple Files (After Refactoring) - Helper 1
A single module can be split across several files. This makes it easier to keep related helpers organized without changing how the module is imported.
module mod1
pub fn hello() {
println('Hello from mod1!')
}
Multiple Files (After Refactoring) - Helper 2 (file2.v)
Multiple Files (After Refactoring) - Helper 2
The second file in the same module can hold additional helper functions. The module still behaves as one logical unit when imported.
module mod1
fn hello2() {
println('Hello 2 from mod1!')
}
Multiple Files (After Refactoring) - Main (modulebasics.v)
Multiple Files (After Refactoring) - Main Entry
Modules help modularize V projects, managing imports and symbol visibility. This lesson on Modulebasics demonstrates code structure, module namespaces, access modifiers, or lifecycle rules.
Additional Context from Repository docs:
This example demonstrates the concepts of modulebasics.
module main
import mod1
fn main() {
mod1.hello()
println('Hello World!')
}
Multiple Files (Before Refactoring) - Helper 1 (file1.v)
Multiple Files (Before Refactoring) - Helper 1
Modules help modularize V projects, managing imports and symbol visibility. This lesson on File1 demonstrates code structure, module namespaces, access modifiers, or lifecycle rules.
Additional Context from Repository docs:
This example demonstrates the concepts of file1.
module mod1
pub fn hello() {
println('Hello from mod1!')
}
Multiple Files (Before Refactoring) - Helper 2 (file2.v)
Multiple Files (Before Refactoring) - Helper 2
Modules help modularize V projects, managing imports and symbol visibility. This lesson on File2 demonstrates code structure, module namespaces, access modifiers, or lifecycle rules.
Additional Context from Repository docs:
This example demonstrates the concepts of file2.
fn hello2() {
println('Hello 2 from mod1!')
}
fn main() {
}
Multiple Files (Before Refactoring) - Main (modulebasics.v)
Multiple Files (Before Refactoring) - Main Entry
Modules help modularize V projects, managing imports and symbol visibility. This lesson on Modulebasics demonstrates code structure, module namespaces, access modifiers, or lifecycle rules.
Additional Context from Repository docs:
This example demonstrates the concepts of modulebasics.
module main
import mod1
fn main() {
mod1.hello()
println('Hello World!')
}
Member Scope (After Refactoring) - Helper 1 (file1.v)
Member Scope (After Refactoring) - Helper 1
A function marked pub can be called from outside the module, while a private helper can still be used by other functions inside the same module.
module mod1
pub fn hello() {
println('Hello from mod1!')
// hello2 is not a public but accessible within mod1
hello2()
}
Member Scope (After Refactoring) - Helper 2 (file2.v)
Member Scope (After Refactoring) - Helper 2
Modules help modularize V projects, managing imports and symbol visibility. This lesson on File2 demonstrates code structure, module namespaces, access modifiers, or lifecycle rules.
Additional Context from Repository docs:
This example demonstrates the concepts of file2.
module mod1
fn hello2() {
println('Hello 2 from mod1!')
}
Member Scope (After Refactoring) - Main (modulebasics.v)
Member Scope (After Refactoring) - Main Entry
Modules help modularize V projects, managing imports and symbol visibility. This lesson on Modulebasics demonstrates code structure, module namespaces, access modifiers, or lifecycle rules.
Additional Context from Repository docs:
This example demonstrates the concepts of modulebasics.
module main
import mod1
fn main() {
mod1.hello()
}
Member Scope (Before Refactoring) - Helper 1 (file1.v)
Member Scope (Before Refactoring) - Helper 1
Modules help modularize V projects, managing imports and symbol visibility. This lesson on File1 demonstrates code structure, module namespaces, access modifiers, or lifecycle rules.
Additional Context from Repository docs:
This example demonstrates the concepts of file1.
module mod1
pub fn hello() {
println('Hello from mod1!')
}
Member Scope (Before Refactoring) - Helper 2 (file2.v)
Member Scope (Before Refactoring) - Helper 2
Modules help modularize V projects, managing imports and symbol visibility. This lesson on File2 demonstrates code structure, module namespaces, access modifiers, or lifecycle rules.
Additional Context from Repository docs:
This example demonstrates the concepts of file2.
module mod1
fn hello2() {
println('Hello 2 from mod1!')
}
Member Scope (Before Refactoring) - Main (modulebasics.v)
Member Scope (Before Refactoring) - Main Entry
Modules help modularize V projects, managing imports and symbol visibility. This lesson on Modulebasics demonstrates code structure, module namespaces, access modifiers, or lifecycle rules.
Additional Context from Repository docs:
This example demonstrates the concepts of modulebasics.
module main
import mod1
fn main() {
mod1.hello()
mod1.hello2()
}
Cyclic Imports - Module 1 Helper (file1.v)
Cyclic Imports - Module 1
This example shows a circular dependency between two modules. In practice, you should avoid this pattern because it makes the import graph harder to reason about.
module m1
import m2
pub const greet_from_m1 = 'Greetings from m1'
pub fn hello() {
println(m2.greet_from_m2)
}
Cyclic Imports - Module 2 Helper (file1.v)
Cyclic Imports - Module 2
Modules help modularize V projects, managing imports and symbol visibility. This lesson on File1 demonstrates code structure, module namespaces, access modifiers, or lifecycle rules.
Additional Context from Repository docs:
This example demonstrates the concepts of file1.
module m2
import m1
pub const greet_from_m2 = 'Greetings from m2'
pub fn hello() {
println(m1.greet_from_m1)
}
Cyclic Imports - Main (modulebasics.v)
Cyclic Imports - Main Entry
Modules help modularize V projects, managing imports and symbol visibility. This lesson on Modulebasics demonstrates code structure, module namespaces, access modifiers, or lifecycle rules.
Additional Context from Repository docs:
This example demonstrates the concepts of modulebasics.
module main
import m1
import m2
fn main() {
m1.hello()
m2.hello()
}
Module Init & Cleanup Functions - Config (config.v)
Transitive Initialization Helper
V modules support lifecycle hooks for setting up and tearing down resources. A module's init() function runs when it is first imported, and its cleanup() function runs when the program terminates.
In this helper module config, we define simple hooks to simulate loading configuration details.
module config
pub const version = '1.0.0'
fn init() {
println('Initializing config module...')
}
fn cleanup() {
println('Cleaning up config module...')
}
Module Init & Cleanup Functions - Helper (file1.v)
Module Init & Cleanup Functions Helper
This helper module mod1 imports config, simulates the initialization/release of a C library wrapper, and exposes a public function hello().
init(): A special private function (fn init()) that runs automatically when a module is first imported. It is ideal for one-time initialization, such as preparing C libraries or setting up state.cleanup(): A special private function (fn cleanup()) that executes automatically when the program terminates. It runs in the reverse order of theinit()calls, making it perfect for releasing C library resources or flushing files.
module mod1
import config
pub fn hello() {
println('Hello from mod1! (using config v${config.version})')
}
fn init() {
println('Initializing mod1 module (C library stub initialized)...')
}
fn cleanup() {
println('Cleaning up mod1 module (C library stub released)...')
}
Module Init & Cleanup Functions - Main (modulebasics.v)
Module Init & Cleanup Functions
V is designed to be highly modular. Here is a summary of the core rules governing V modules, and how the program executes:
1. Module Basics & Organization
- Scope: Every file in a directory belongs to the same module. If no module name is specified at the top of the file, it defaults to
main. - Visibility: All elements (structs, functions, constants, etc.) inside a module are visible across all files of that same module, regardless of whether they are marked with
pub. - Names: Module names must be short (ideally under 10 characters) and written in
snake_case. - Circular Imports: Circular imports are strictly forbidden.
2. Module Lookup & `v.mod`
- V uses
v.modfiles as lookup anchors. - The directory containing the nearest
v.modfile is prepended to V's module search path. This enables projects to easily import submodules (e.g.import myapp.common) using relative structure anchors.
3. Special Prototyping Rules for Project Roots
- For the top-level project folder (compiled with
v .), you can have multiple.vfiles belonging to different modules (likemodule mainandmodule abc) in the same directory. - This is a special rule designed to ease prototyping, allowing you to split files easily before moving them to separate directory submodules. In any other non-root directory, all
.vfiles must declare the exact same module name matching the folder name.
4. Lifecycle Hooks (`init` & `cleanup`)
- Neither
init()norcleanup()can be made public (pub). - Single Execution: V calls
init()exactly once when the module is imported, regardless of how many other modules transitively or directly import it. For example,configis imported by bothmod1andmain, but itsinit()runs only once. - Reverse-Order Execution: V calls
cleanup()automatically once at the end of program execution, in the exact reverse order of theirinit()invocations.
module main
import mod1
import config
fn main() {
println('Main function started.')
mod1.hello()
println('Using config directly in main: v${config.version}')
println('Main function ending.')
}
Accessing Module Constants - Helper (file1.v)
Accessing Module Constants Helper
Constants are shared values that belong to a module. They are great for configuration strings or fixed messages that multiple files can use.
module mod1
pub const greet_msg = 'Greeting from mod1!'
Accessing Module Constants - Main (modulebasics.v)
Accessing Module Constants
Modules help modularize V projects, managing imports and symbol visibility. This lesson on Modulebasics demonstrates code structure, module namespaces, access modifiers, or lifecycle rules.
Additional Context from Repository docs:
This example demonstrates the concepts of modulebasics.
module main
import mod1
fn main() {
println(mod1.greet_msg)
}
Accessing Module Structs - Helper (file1.v)
Accessing Module Structs Helper
A struct is a user-defined custom type that groups related variables (called fields) together. Structs are fundamental to V's object-oriented programming model. By default, struct fields are private and immutable. V provides access modifiers like mut:, pub:, and pub mut: to control field access and mutability.
These examples demonstrate defining structs, updating fields, required fields, default values, and struct methods.
Additional Context from Repository docs:
This example demonstrates the concepts of file1.
module mod1
import time
// NoteTimeInfo is a struct to store time info of Note
pub struct NoteTimeInfo {
pub:
created time.Time = time.now()
pub mut:
due time.Time = time.now().add_days(1)
}
// Note is a struct with embedding struct NoteTimeInfo along with other fields
pub struct Note {
NoteTimeInfo // Embedded Struct
pub:
id int
pub mut:
message string @[required]
status bool
}
Accessing Module Structs - Main (modulebasics.v)
Accessing Module Structs
A struct is a user-defined custom type that groups related variables (called fields) together. Structs are fundamental to V's object-oriented programming model. By default, struct fields are private and immutable. V provides access modifiers like mut:, pub:, and pub mut: to control field access and mutability.
These examples demonstrate defining structs, updating fields, required fields, default values, and struct methods.
Additional Context from Repository docs:
This example demonstrates the concepts of modulebasics.
module main
import mod1
fn main() {
n := mod1.Note{
id: 1
message: 'Accessing structs of module demo'
}
println('Accessing struct field value Note id: ${n.id}')
println('Accessing embedded struct field value NoteTimeInfo: ${n.NoteTimeInfo}')
}
Installing External Packages
V has a built-in package manager called vpm (V Package Manager) that allows you to easily install, update, and manage third-party modules. External packages are hosted on the official V registry at vpm.vlang.io.
How to Install Packages with vpm
To install a package, use the v install command followed by the package identifier (usually in the format author.package_name):
v install xiusin.vredis
This downloads the package and installs it into the V modules directory (typically located at ~/.vmodules/ on Linux/macOS or C:\Users\Username\.vmodules\ on Windows).
Common vpm Commands
- Install a package:
v install author.package_name
- Install from a Git repository directly:
v install https://github.com/author/package_name
- Update an installed package:
v update author.package_name
- Remove/uninstall a package:
v remove author.package_name
- Search for packages:
v search query
Importing and Using External Packages
Once a package is installed via vpm, you can import it in your V code just like a standard library module:
import xiusin.vredis
fn main() {
// Code utilizing the external redis package
}
Redis Console Demo
This example demonstrates how to use the external xiusin.vredis client package in a console application and demonstrates key namespacing with the custom NamespacedRedis helper. It covers:
- Establishing a connection and handling errors gracefully.
- Basic String operations (
set,get,incr,expire,ttl,del). - List operations (
rpush,llen,lrange,lpop). - Hash operations (
hset,hget,hgetall). - Set operations (
sadd,sismember,smembers). - Namespaced Redis helper operations using
NamespacedRedis. - Cleaning up created keys on application exit.
module main
import xiusin.vredis
fn main() {
println('==================================================')
println(' V + Redis Console API Learning Demo ')
println('==================================================')
println('Connecting to local Redis at 127.0.0.1:6379...')
mut r := vredis.new_client(host: '127.0.0.1', port: 6379) or {
eprintln('\n[ERROR] Failed to connect to Redis server: ${err}')
eprintln('Please make sure Redis is running locally on port 6379.')
return
}
defer {
r.close() or {}
println('\n==================================================')
println('Demo completed. Redis connection closed.')
println('==================================================')
}
println('Connected successfully!\n')
// Clean up any old test keys first
r.del('demo:string') or {}
r.del('demo:counter') or {}
r.del('demo:list') or {}
r.del('demo:hash') or {}
r.del('demo:set') or {}
// --- 1. String Operations ---
println('--- 1. String Operations ---')
println('Setting "demo:string" to "Hello V + Redis!"...')
r.set('demo:string', 'Hello V + Redis!') or { panic(err) }
val := r.get('demo:string') or { panic(err) }
println('GET "demo:string" -> "${val}"')
// Increment demo
r.incr('demo:counter') or { panic(err) }
r.incr('demo:counter') or { panic(err) }
counter_val := r.get('demo:counter') or { panic(err) }
println('Counter INCR twice -> "${counter_val}"')
// TTL Demo
println('Setting expiry of 5 seconds on "demo:string"...')
r.expire('demo:string', 5) or { panic(err) }
ttl_val := r.ttl('demo:string') or { panic(err) }
println('TTL remaining: ${ttl_val} seconds\n')
// --- 2. List Operations ---
println('--- 2. List Operations ---')
println('Pushing items to "demo:list" (item_a, item_b, item_c)...')
r.rpush('demo:list', 'item_a') or { panic(err) }
r.rpush('demo:list', 'item_b') or { panic(err) }
r.rpush('demo:list', 'item_c') or { panic(err) }
list_len := r.llen('demo:list') or { panic(err) }
println('List length: ${list_len}')
list_items := r.lrange('demo:list', 0, -1) or { panic(err) }
println('List elements: ${list_items}')
popped := r.lpop('demo:list') or { panic(err) }
println('Popped from left (LPOP): "${popped}"')
list_items_after := r.lrange('demo:list', 0, -1) or { panic(err) }
println('List elements after LPOP: ${list_items_after}\n')
// --- 3. Hash Operations ---
println('--- 3. Hash Operations ---')
println('Setting fields in "demo:hash"...')
r.hset('demo:hash', 'name', 'V Programming Language') or { panic(err) }
r.hset('demo:hash', 'year', '2019') or { panic(err) }
r.hset('demo:hash', 'creator', 'Alex Medvednikov') or { panic(err) }
name_field := r.hget('demo:hash', 'name') or { panic(err) }
println('HGET "demo:hash" "name" -> "${name_field}"')
hash_all := r.hgetall('demo:hash') or { panic(err) }
println('HGETALL "demo:hash" fields & values:')
for k, v in hash_all {
println(' - ${k}: ${v}')
}
println('')
// --- 4. Set Operations ---
println('--- 4. Set Operations ---')
println('Adding members to "demo:set"...')
r.sadd('demo:set', 'apple') or { panic(err) }
r.sadd('demo:set', 'banana') or { panic(err) }
r.sadd('demo:set', 'apple') or { panic(err) } // Duplicate (should be ignored)
is_banana := r.sismember('demo:set', 'banana') or { panic(err) }
is_cherry := r.sismember('demo:set', 'cherry') or { panic(err) }
println('SISMEMBER "demo:set" "banana": ${is_banana}')
println('SISMEMBER "demo:set" "cherry": ${is_cherry}')
set_members := r.smembers('demo:set') or { panic(err) }
println('SMEMBERS "demo:set": ${set_members}\n')
// --- 5. Namespaced Helper Demo ---
println('--- 5. Namespaced Helper Demo ---')
println('Creating a namespaced helper with namespace "app_v1"...')
mut nr := new_namespaced_redis(r, 'app_v1')
println('Setting namespaced key "user_token" (resolved key will be "app_v1:user_token")...')
nr.set('user_token', 'token_abc123') or { panic(err) }
token := nr.get('user_token') or { panic(err) }
println('GET "user_token" via helper -> "${token}"')
// Verify the actual key in Redis (without namespace helper) has the prefix
actual_key := 'app_v1:user_token'
actual_val := r.get(actual_key) or { panic(err) }
println('GET raw "${actual_key}" directly from client -> "${actual_val}"')
// Cleanup namespaced keys
println('Cleaning up namespaced keys...')
nr.del('user_token') or {}
// Clean up test keys
println('\nCleaning up created keys...')
r.del('demo:string') or {}
r.del('demo:counter') or {}
r.del('demo:list') or {}
r.del('demo:hash') or {}
r.del('demo:set') or {}
println('Cleanup done.')
}
Redis Console Demo - Helper (redis_helper.v)
This helper provides a namespaced wrapper struct NamespacedRedis that automatically prefixes all Redis keys with a given namespace (e.g. namespace:key). This is a great pattern for keeping keys organized and avoiding collisions between multiple apps/environments.
module main
import xiusin.vredis
// NamespacedRedis wraps a standard vredis.Redis client and prefixes all keys with a namespace.
// This simplifies multi-tenant or multi-app key separation.
struct NamespacedRedis {
mut:
client &vredis.Redis
pub:
namespace string
}
// new_namespaced_redis creates a new NamespacedRedis helper wrapper.
fn new_namespaced_redis(client &vredis.Redis, namespace string) NamespacedRedis {
return NamespacedRedis{
client: client
namespace: namespace
}
}
// key constructs the final namespaced key.
// E.g. key('mykey') -> 'app1:mykey'
fn (nr NamespacedRedis) key(name string) string {
if nr.namespace == '' {
return name
}
return '${nr.namespace}:${name}'
}
// close closes the connection to the Redis server.
fn (mut nr NamespacedRedis) close() ! {
nr.client.close()!
}
// --- String Operations ---
// set sets a key to a string value.
fn (mut nr NamespacedRedis) set(key string, val string) ! {
nr.client.set(nr.key(key), val)!
}
// get retrieves a string value by key.
fn (mut nr NamespacedRedis) get(key string) !string {
return nr.client.get(nr.key(key))!
}
// incr increments a numeric key.
fn (mut nr NamespacedRedis) incr(key string) ! {
nr.client.incr(nr.key(key))!
}
// expire sets an expiration time (TTL) in seconds on a key.
fn (mut nr NamespacedRedis) expire(key string, seconds int) ! {
nr.client.expire(nr.key(key), seconds)!
}
// ttl returns the remaining Time-To-Live of a key.
fn (mut nr NamespacedRedis) ttl(key string) !int {
return nr.client.ttl(nr.key(key))!
}
// del deletes a key.
fn (mut nr NamespacedRedis) del(key string) ! {
nr.client.del(nr.key(key))!
}
// --- List Operations ---
// rpush appends a value to a list.
fn (mut nr NamespacedRedis) rpush(key string, val string) ! {
nr.client.rpush(nr.key(key), val)!
}
// lrange retrieves a range of elements from a list.
fn (mut nr NamespacedRedis) lrange(key string, start int, stop int) ![]string {
return nr.client.lrange(nr.key(key), start, stop)!
}
// lpop removes and returns the first element of a list.
fn (mut nr NamespacedRedis) lpop(key string) !string {
return nr.client.lpop(nr.key(key))!
}
// llen returns the length of a list.
fn (mut nr NamespacedRedis) llen(key string) !int {
return nr.client.llen(nr.key(key))!
}
// --- Hash Operations ---
// hset sets a field in a hash to a value.
fn (mut nr NamespacedRedis) hset(key string, field string, val string) ! {
nr.client.hset(nr.key(key), field, val)!
}
// hget retrieves a field's value from a hash.
fn (mut nr NamespacedRedis) hget(key string, field string) !string {
return nr.client.hget(nr.key(key), field)!
}
// hgetall retrieves all fields and values of a hash.
fn (mut nr NamespacedRedis) hgetall(key string) !map[string]string {
return nr.client.hgetall(nr.key(key))!
}
// --- Set Operations ---
// sadd adds a member to a set.
fn (mut nr NamespacedRedis) sadd(key string, member string) ! {
nr.client.sadd(nr.key(key), member)!
}
// sismember checks if a member belongs to a set.
fn (mut nr NamespacedRedis) sismember(key string, member string) !bool {
return nr.client.sismember(nr.key(key), member)!
}
// smembers returns all members of a set.
fn (mut nr NamespacedRedis) smembers(key string) ![]string {
return nr.client.smembers(nr.key(key))!
}
Redis Namespaced Demo - Helper (redis_helper.v)
Redis Namespaced Helper
Modules help modularize V projects, managing imports and symbol visibility. This lesson on Redis Helper demonstrates code structure, module namespaces, access modifiers, or lifecycle rules.
module main
import xiusin.vredis
// NamespacedRedis wraps a standard vredis.Redis client and prefixes all keys with a namespace.
struct NamespacedRedis {
mut:
client &vredis.Redis
pub:
namespace string
}
// new_namespaced_redis creates a new NamespacedRedis helper wrapper.
fn new_namespaced_redis(client &vredis.Redis, namespace string) NamespacedRedis {
return NamespacedRedis{
client: client
namespace: namespace
}
}
// key constructs the final namespaced key.
fn (nr NamespacedRedis) key(name string) string {
if nr.namespace == '' {
return name
}
return '${nr.namespace}:${name}'
}
// close closes the connection to the Redis server.
fn (mut nr NamespacedRedis) close() ! {
nr.client.close()!
}
// --- String Operations ---
// set sets a key to a string value.
fn (mut nr NamespacedRedis) set(key string, val string) ! {
nr.client.set(nr.key(key), val)!
}
// get retrieves a string value by key.
fn (mut nr NamespacedRedis) get(key string) !string {
return nr.client.get(nr.key(key))!
}
// incr increments a numeric key.
fn (mut nr NamespacedRedis) incr(key string) ! {
nr.client.incr(nr.key(key))!
}
// expire sets an expiration time (TTL) in seconds on a key.
fn (mut nr NamespacedRedis) expire(key string, seconds int) ! {
nr.client.expire(nr.key(key), seconds)!
}
// ttl returns the remaining Time-To-Live of a key.
fn (mut nr NamespacedRedis) ttl(key string) !int {
return nr.client.ttl(nr.key(key))!
}
// del deletes a key.
fn (mut nr NamespacedRedis) del(key string) ! {
nr.client.del(nr.key(key))!
}
// --- List Operations ---
// rpush appends a value to a list.
fn (mut nr NamespacedRedis) rpush(key string, val string) ! {
nr.client.rpush(nr.key(key), val)!
}
// lrange retrieves a range of elements from a list.
fn (mut nr NamespacedRedis) lrange(key string, start int, stop int) ![]string {
return nr.client.lrange(nr.key(key), start, stop)!
}
// lpop removes and returns the first element of a list.
fn (mut nr NamespacedRedis) lpop(key string) !string {
return nr.client.lpop(nr.key(key))!
}
// llen returns the length of a list.
fn (mut nr NamespacedRedis) llen(key string) !int {
return nr.client.llen(nr.key(key))!
}
// --- Hash Operations ---
// hset sets a field in a hash to a value.
fn (mut nr NamespacedRedis) hset(key string, field string, val string) ! {
nr.client.hset(nr.key(key), field, val)!
}
// hget retrieves a field's value from a hash.
fn (mut nr NamespacedRedis) hget(key string, field string) !string {
return nr.client.hget(nr.key(key), field)!
}
// hgetall retrieves all fields and values of a hash.
fn (mut nr NamespacedRedis) hgetall(key string) !map[string]string {
return nr.client.hgetall(nr.key(key))!
}
// --- Set Operations ---
// sadd adds a member to a set.
fn (mut nr NamespacedRedis) sadd(key string, member string) ! {
nr.client.sadd(nr.key(key), member)!
}
// sismember checks if a member belongs to a set.
fn (mut nr NamespacedRedis) sismember(key string, member string) !bool {
return nr.client.sismember(nr.key(key), member)!
}
// smembers returns all members of a set.
fn (mut nr NamespacedRedis) smembers(key string) ![]string {
return nr.client.smembers(nr.key(key))!
}
Redis Namespaced Demo
This example provides an easy, dedicated demo showing how to use the NamespacedRedis helper wrapper to manage multiple independent namespaces (like cache and session) over a single underlying Redis connection without key collisions.
module main
import xiusin.vredis
fn main() {
println('==================================================')
println(' V + Redis Namespaced Helper Easy Demo ')
println('==================================================')
println('Connecting to local Redis at 127.0.0.1:6379...')
mut client := vredis.new_client(host: '127.0.0.1', port: 6379) or {
eprintln('\n[ERROR] Failed to connect to Redis server: ${err}')
eprintln('Please make sure Redis is running locally on port 6379.')
return
}
defer {
client.close() or {}
println('\n==================================================')
println('Demo completed. Redis connection closed.')
println('==================================================')
}
println('Connected successfully!\n')
// Create a namespaced client wrapper for "cache"
println('Initializing "cache" namespace wrapper...')
mut cache := new_namespaced_redis(client, 'cache')
// Create another namespaced client wrapper for "session"
println('Initializing "session" namespace wrapper...\n')
mut session := new_namespaced_redis(client, 'session')
// 1. Store value in cache namespace (key will be "cache:user_123")
println('1. Storing data in "cache" namespace (key: "user_123")...')
cache.set('user_123', '{"name": "Alice", "role": "Admin"}') or { panic(err) }
// 2. Store value in session namespace (key will be "session:user_123")
println('2. Storing data in "session" namespace (key: "user_123")...')
session.set('user_123', 'active_session_token_xyz987') or { panic(err) }
println('\n--- Retrieval ---')
// 3. Retrieve values using the namespace helpers
cache_val := cache.get('user_123') or { panic(err) }
session_val := session.get('user_123') or { panic(err) }
println('Retrieved from cache: "${cache_val}"')
println('Retrieved from session: "${session_val}"')
println('\n--- Verification (Direct Raw Lookups) ---')
// 4. Retrieve values using the raw client directly to show the actual keys stored
raw_cache := client.get('cache:user_123') or { panic(err) }
raw_session := client.get('session:user_123') or { panic(err) }
println('Raw key "cache:user_123" directly: "${raw_cache}"')
println('Raw key "session:user_123" directly: "${raw_session}"')
// Cleanup
println('\nCleaning up keys...')
cache.del('user_123') or {}
session.del('user_123') or {}
println('Cleanup done.')
}
Redis Webview Demo
Redis Webview Demo
Modules help modularize V projects, managing imports and symbol visibility. This lesson on Redis Webview Demo demonstrates code structure, module namespaces, access modifiers, or lifecycle rules.
module main
import json
import ttytm.webview
import xiusin.vredis
struct KeyInfo {
name string
@type string
ttl int
}
struct KeyDetail {
mut:
name string
@type string
ttl int
value string
list_val []string
hash_val map[string]string
}
struct ConnectStatus {
status string
host string
port int
version string
keys_count int
}
// Embed the HTML, CSS, and JS file directly into the binary
const html_file = $embed_file('index.html')
const html = html_file.to_string()
fn connect_redis() !&vredis.Redis {
return vredis.new_client(host: '127.0.0.1', port: 6379)
}
fn redis_connect_status(e &webview.Event) !string {
mut client := connect_redis() or {
status_info := ConnectStatus{
status: 'disconnected'
host: '127.0.0.1'
port: 6379
version: ''
keys_count: 0
}
return json.encode(status_info)
}
defer {
client.close() or {}
}
mut version := 'Unknown'
info := client.send('INFO', 'server') or {
count := client.dbsize() or { 0 }
status_info := ConnectStatus{
status: 'connected'
host: '127.0.0.1'
port: 6379
version: 'Unknown'
keys_count: count
}
return json.encode(status_info)
}
if info.bytestr().len > 0 {
lines := info.bytestr().split('\n')
for line in lines {
if line.starts_with('redis_version:') {
parts := line.split(':')
if parts.len >= 2 {
version = parts[1].trim_space()
}
break
}
}
}
count := client.dbsize() or { 0 }
status_info := ConnectStatus{
status: 'connected'
host: '127.0.0.1'
port: 6379
version: version
keys_count: count
}
return json.encode(status_info)
}
fn redis_get_keys(e &webview.Event) !string {
mut client := connect_redis()!
defer {
client.close() or {}
}
keys := client.keys('*') or { []string{} }
mut items := []KeyInfo{}
for key in keys {
t := client.@type(key) or { 'unknown' }
ttl := client.ttl(key) or { -1 }
items << KeyInfo{
name: key
@type: t
ttl: ttl
}
}
return json.encode(items)
}
fn redis_get_key_detail(e &webview.Event) !string {
mut client := connect_redis()!
defer {
client.close() or {}
}
key := e.get_arg[string](0)!
t := client.@type(key)!
ttl := client.ttl(key)!
mut detail := KeyDetail{
name: key
@type: t
ttl: ttl
value: ''
list_val: []string{}
hash_val: map[string]string{}
}
match t {
'string' {
detail.value = client.get(key) or { '' }
}
'list' {
detail.list_val = client.lrange(key, 0, -1) or { []string{} }
}
'set' {
detail.list_val = client.smembers(key) or { []string{} }
}
'hash' {
detail.hash_val = client.hgetall(key) or {
map[string]string{}
}
}
else {}
}
return json.encode(detail)
}
fn redis_set_string(e &webview.Event) !string {
mut client := connect_redis()!
defer {
client.close() or {}
}
key := e.get_arg[string](0)!
val := e.get_arg[string](1)!
ttl := e.get_arg[int](2)!
client.set(key, val)!
if ttl > 0 {
client.expire(key, ttl)!
} else if ttl == -1 {
client.persist(key) or {}
}
return 'ok'
}
fn redis_set_list(e &webview.Event) !string {
mut client := connect_redis()!
defer {
client.close() or {}
}
key := e.get_arg[string](0)!
vals_json := e.get_arg[string](1)!
ttl := e.get_arg[int](2)!
vals := json.decode([]string, vals_json)!
client.del(key) or {}
for val in vals {
client.rpush(key, val)!
}
if ttl > 0 {
client.expire(key, ttl)!
} else if ttl == -1 {
client.persist(key) or {}
}
return 'ok'
}
fn redis_set_hash(e &webview.Event) !string {
mut client := connect_redis()!
defer {
client.close() or {}
}
key := e.get_arg[string](0)!
hash_json := e.get_arg[string](1)!
ttl := e.get_arg[int](2)!
fvs := json.decode(map[string]string, hash_json)!
client.del(key) or {}
for field, val in fvs {
client.hset(key, field, val)!
}
if ttl > 0 {
client.expire(key, ttl)!
} else if ttl == -1 {
client.persist(key) or {}
}
return 'ok'
}
fn redis_set_set(e &webview.Event) !string {
mut client := connect_redis()!
defer {
client.close() or {}
}
key := e.get_arg[string](0)!
vals_json := e.get_arg[string](1)!
ttl := e.get_arg[int](2)!
vals := json.decode([]string, vals_json)!
client.del(key) or {}
for val in vals {
client.sadd(key, val)!
}
if ttl > 0 {
client.expire(key, ttl)!
} else if ttl == -1 {
client.persist(key) or {}
}
return 'ok'
}
fn redis_del_key(e &webview.Event) !string {
mut client := connect_redis()!
defer {
client.close() or {}
}
key := e.get_arg[string](0)!
client.del(key)!
return 'ok'
}
fn redis_flush_db(e &webview.Event) !string {
mut client := connect_redis()!
defer {
client.close() or {}
}
client.flushdb()!
return 'ok'
}
fn main() {
mut w := webview.create(debug: true)
defer {
w.destroy()
}
w.set_title('V + Redis GUI Dashboard')
w.set_size(1080, 720, .@none)
// Bindings
w.bind_opt[string]('redis_connect_status', redis_connect_status)
w.bind_opt[string]('redis_get_keys', redis_get_keys)
w.bind_opt[string]('redis_get_key_detail', redis_get_key_detail)
w.bind_opt[string]('redis_set_string', redis_set_string)
w.bind_opt[string]('redis_set_list', redis_set_list)
w.bind_opt[string]('redis_set_hash', redis_set_hash)
w.bind_opt[string]('redis_set_set', redis_set_set)
w.bind_opt[string]('redis_del_key', redis_del_key)
w.bind_opt[string]('redis_flush_db', redis_flush_db)
w.set_html(html)
w.run()
}
Webview Demo
Webview Demo
Modules help modularize V projects, managing imports and symbol visibility. This lesson on Webview Demo demonstrates code structure, module namespaces, access modifiers, or lifecycle rules.
Additional Context from Repository docs:
This example demonstrates the concepts of installing external packages and webview bindings.
module main
import ttytm.webview
const html = '
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: linear-gradient(135deg, #1e1e2f 0%, #111119 100%);
color: #f8f8f2;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
user-select: none;
}
.container {
text-align: center;
background: rgba(255, 255, 255, 0.05);
padding: 30px;
border-radius: 12px;
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3);
backdrop-filter: blur(4px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
h1 {
margin-bottom: 20px;
font-size: 2.2rem;
color: #50fa7b;
}
input {
padding: 10px 15px;
font-size: 1rem;
border-radius: 6px;
border: 1px solid #6272a4;
background-color: #282a36;
color: #f8f8f2;
margin-right: 10px;
outline: none;
}
button {
padding: 10px 20px;
font-size: 1rem;
font-weight: bold;
color: #282a36;
background-color: #50fa7b;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
}
button:hover {
background-color: #8be9fd;
transform: translateY(-1px);
}
#result {
margin-top: 25px;
font-size: 1.1rem;
min-height: 25px;
color: #f1fa8c;
}
</style>
</head>
<body>
<div class="container">
<h1>V + Webview Binding</h1>
<input type="text" id="userInput" placeholder="Enter message for V..." value="Hello from JS!">
<button onclick="sendToV()">Send to V</button>
<div id="result">Waiting for action...</div>
</div>
<script>
async function sendToV() {
const input = document.getElementById("userInput").value;
const resultDiv = document.getElementById("result");
resultDiv.innerText = "Calling V function...";
try {
// Call the bound V function "greet_from_v" asynchronously
const res = await window.greet_from_v(input);
resultDiv.innerText = res;
} catch (err) {
resultDiv.innerText = "Error: " + err;
}
}
</script>
</body>
</html>
'
// V binding function. Must take &webview.Event and can return a type (like string).
fn greet_from_v(e &webview.Event) string {
// 1. Retrieve the argument passed from JavaScript (at index 0)
msg := e.get_arg[string](0) or { 'No arguments passed' }
println('V side: Received from JS: ${msg}')
// 2. We can run custom JavaScript on the webview page from V
e.eval('console.log("V successfully invoked eval in JS context!");')
// 3. Return string back to the JS Promise resolver
return 'V responds: "Message received: ${msg}"'
}
fn main() {
// Initialize Webview
mut w := webview.create(debug: true)
w.set_title('V Webview Binding Demo')
w.set_size(600, 450, .@none)
// Bind V function "greet_from_v" to JS window.greet_from_v
w.bind('greet_from_v', greet_from_v)
// Load the HTML content
w.set_html(html)
// Run the main loop
w.run()
}
Packaging Webview as macOS Binaries
Packaging Webview as macOS Binaries
V can run JavaScript, HTML, and CSS under the hood inside its lightweight webview bindings to build impressive cross-platform desktop GUIs. Once your webview-based application is ready to ship, you can package it as a standalone, production-ready macOS app bundle.
To make this seamless, you can use vlang_macos_webview_app_template. This is a pure-V template framework featuring a specialized build tool build.vsh that runs native macOS utilities (such as sips and iconutil) to assemble, compile, and structure your app without any Node or Bun JavaScript toolchain requirements.
Step-by-Step Packaging Guide
To package your webview app, follow the instructions below:
1. Prerequisites and Installation
Ensure you have the V compiler installed and ttytm.webview registered:
# Install the webview library
v install ttytm.webview
# Clone the packaging template repository
git clone https://github.com/codecaine-zz/vlang_macos_webview_app_template.git
cd vlang_macos_webview_app_template
2. Running Locally under Development
To execute your webview application inline without compilation to verify behaviors:
v run main.v
3. Packaging as a Default macOS App Bundle
To compile main.v with full release-mode optimizations (-prod) and package it using the default name and default wave icon:
v run build.vsh
This compiles your application and structures:
dist/Vlang Macos Webview App Template.app
4. Custom App Packaging (Custom Name, Icon, and ID)
You can customize the compilation by specifying command flags to customize display names, custom bundle identifiers, and apply any of the 101 built-in glassmorphism workspace icons:
v run build.vsh main.v --name "My custom App" --icon resources/developer.png --identifier "com.example.myapp"
Available build parameters:
-i, --icon <path>: Path to a PNG icon. Defaults to resources/icon.png or icon.png.-n, --name <name>: Custom display name for the .app bundle wrapper.-d, --identifier <id>: CFBundleIdentifier mapping (e.g. com.example.id).-v, --version <version>: App version (defaults to version in v.mod, or 1.0.0).-o, --out <dir>: Custom destination output directory (defaults to dist).
Example building with local premium glassmorphism icons:
# Build an IDE app using the Developer icon template
v run build.vsh --name "Code Studio" --icon resources/developer.png
# Build a database tool using the Database Admin icon
v run build.vsh --name "DB Browser" --icon resources/database_admin.png
# Build a task planner using the Kanban Board icon
v run build.vsh --name "Task Board" --icon resources/kanban_board.png
5. Running the Packaged macOS App
Once built, you can run and distribute your app bundle by:
- Double-clicking the .app bundle inside Finder (located in the dist/ output directory).
- Launching it from the terminal:
open "dist/Vlang Macos Webview App Template.app"
Using premium templates like this ensures your compiled V webview apps have zero runtime bloat and deliver a sleek, fully native feel matches Apple's premium macOS Sequoia glassmorphism specifications.
Selective Imports
Selective Imports
V permits you to selectively import specific public functions and types directly from a module by using the syntax import module_name { symbol1, symbol2 }. This allows calling those symbols directly without the module prefix. Note that selective imports are not permitted for module constants, which must always be prefixed.
module main
import os { input, user_os }
fn main() {
println('=== Selective Imports ===')
// We can use the imported functions directly without os. prefix:
name := 'Ada'
println('Hello, ${name}!')
current_os := user_os()
println('Your OS is ${current_os}.')
assert current_os.len > 0
}
Module Hierarchy
File locations: modules/ch13_module_hierarchy/abc/file1.v, modules/ch13_module_hierarchy/abc/def/file2.v, modules/ch13_module_hierarchy/modulebasics.v
Module Hierarchy
Modules in V map directly to the directory hierarchy. However, nested directory hierarchies are resolved in a flattened namespace. A submodule located in abc/def/source.v is declared with module def (not module abc.def), but must be imported via import abc.def and its public symbols are called using a single prefix: def.func().
Module Helper (`abc/file1.v`):
module abc
pub fn hello_abc() {
println('Hello from abc!')
}
Submodule Helper (`abc/def/file2.v`):
module def
pub fn hello_def() {
println('Hello from def!')
}
Main Entry (`modulebasics.v`):
module main
import abc
import abc.def
fn main() {
println('=== Module Hierarchy ===')
abc.hello_abc()
def.hello_def() // Call with def, not abc.def
}
Module Import Aliasing
File locations: modules/ch14_module_import_aliasing/mymod/sha256/sha256.v, modules/ch14_module_import_aliasing/modulebasics.v
Module Import Aliasing
When you have module naming conflicts (for example, importing two different modules named sha256), you can resolve them by aliasing one or both using the as keyword:
Custom SHA256 Helper (`mymod/sha256/sha256.v`):
module sha256
pub fn sum(data []u8) string {
return 'mock_sha256_sum_for_aliasing_demo'
}
Main Entry (`modulebasics.v`):
module main
import crypto.sha256
import mymod.sha256 as mysha256
fn main() {
println('=== Module Import Aliasing ===')
// Use the standard crypto.sha256:
v_hash := sha256.sum('hi'.bytes()).hex()
// Use our aliased mymod.sha256:
my_hash := mysha256.sum('hi'.bytes())
println('Standard hash: ${v_hash}')
println('Aliased mymod hash: ${my_hash}')
assert my_hash == 'mock_sha256_sum_for_aliasing_demo'
}
Chapter 10 Writing Tests in V
Quick Access
Below is an index of all code examples in this chapter. You can use these links to jump directly to any specific code example:
Assertions & Unit Testing
- Assert Demo
- Simple Test - Before (demo_test.v)
- Simple Test - After (demo_test.v)
- Testsuite Demo Test
- Testing Optional Return Functions (demo_test.v)
- Greet
- Greet Test
- Main Test
- Testing Program Modules - Helper (file1.v)
- Mod1 Test
- Modulebasics
V has testing built directly into the compiler. This chapter explains how to write test files, use assertions, set up test suites with setup/teardown methods, and run test suites.
Assertions & Unit Testing
Assert Demo
Assert Demo
V has built-in testing support. Any file ending with _test.v is considered a test file. Inside test files, you write functions starting with test_ and use assert statements to check if conditions are true. You can run all tests in a folder using the v test . command.
These examples cover writing simple assertions, test suites, and testing functions that return options or errors.
Additional Context from Repository docs:
This example demonstrates the concepts of assert demo.
module main
fn main() {
println('1st assert')
msg := 'hello there!'
assert msg.contains('hello') // true
println('2nd assert')
assert 'apple' == 'orange' // stops execution
println('done')
}
Asserts with an Extra Message
Asserts with an Extra Message
V allows appending a custom error message to assert statements using a comma: assert condition, 'custom error message'. When the assertion fails, this message will be printed to help troubleshoot the failure.
module main
fn main() {
println('=== Assert with Message ===')
for i in 0 .. 5 {
// This assertion is true for all i < 5, but demonstrates how to supply a message
assert i * 2 < 10, 'assertion failed for i: ${i}'
}
println('All assertions passed!')
}
Asserts That Do Not Abort Your Program
Asserts That Do Not Abort Your Program
By default, an assertion failure immediately terminates the running program. If you are prototyping or running tests where you want all assertion failures to be reported without halting execution, you can tag the containing function with the @[assert_continues] attribute:
module main
@[assert_continues]
fn check_value(ii int) {
assert ii == 2
}
fn main() {
println('=== Assert Continues ===')
for i in 0 .. 4 {
check_value(i)
}
println('Finished running!')
}
Simple Test - Before (demo_test.v)
Simple Test - Before
V has built-in testing support. Any file ending with _test.v is considered a test file. Inside test files, you write functions starting with test_ and use assert statements to check if conditions are true. You can run all tests in a folder using the v test . command.
These examples cover writing simple assertions, test suites, and testing functions that return options or errors.
Additional Context from Repository docs:
This example demonstrates the concepts of demo test.
fn test_first() {
assert 2 != 2
}
Simple Test - After (demo_test.v)
Simple Test - After
V has built-in testing support. Any file ending with _test.v is considered a test file. Inside test files, you write functions starting with test_ and use assert statements to check if conditions are true. You can run all tests in a folder using the v test . command.
These examples cover writing simple assertions, test suites, and testing functions that return options or errors.
Additional Context from Repository docs:
This example demonstrates the concepts of demo test.
fn test_first() {
assert 2 == 2
}
Testsuite Demo Test
Testsuite Demo Test
V has built-in testing support. Any file ending with _test.v is considered a test file. Inside test files, you write functions starting with test_ and use assert statements to check if conditions are true. You can run all tests in a folder using the v test . command.
These examples cover writing simple assertions, test suites, and testing functions that return options or errors.
Additional Context from Repository docs:
This example demonstrates the concepts of testsuite demo test.
import os
fn testsuite_begin() {
os.setenv('foo', 'bar', true)
println('About to start executing all tests')
}
fn test_env_foo_has_value_bar() {
println('Executing test')
// arrange
inp := 'foo'
expected := 'bar'
// act
actual := os.getenv(inp)
// assert
assert actual == expected
}
fn testsuite_end() {
os.unsetenv('foo')
println('Finished executing all tests')
}
Testing Optional Return Functions (demo_test.v)
Testing Optional Return Functions
V has built-in testing support. Any file ending with _test.v is considered a test file. Inside test files, you write functions starting with test_ and use assert statements to check if conditions are true. You can run all tests in a folder using the v test . command.
These examples cover writing simple assertions, test suites, and testing functions that return options or errors.
Additional Context from Repository docs:
This example demonstrates the concepts of demo test.
fn greet(name string) !string {
if name != '' {
return 'Hello ${name}!'
}
return error('name not provided')
}
fn test_greet_given_a_name() {
exp := 'Hello Pavan!'
assert (greet('Pavan') or { err.msg() }) == exp
}
fn test_greet_propagates_error() ! {
res := greet('Pavan')!
assert res == 'Hello Pavan!'
}
fn test_greet_when_empty() {
exp := 'name not provided'
assert (greet('') or { err.msg() }) == exp
}
Greet
Greet
V has built-in testing support. Any file ending with _test.v is considered a test file. Inside test files, you write functions starting with test_ and use assert statements to check if conditions are true. You can run all tests in a folder using the v test . command.
These examples cover writing simple assertions, test suites, and testing functions that return options or errors.
Additional Context from Repository docs:
This example demonstrates the concepts of greet.
module main
fn greet(name string) string {
return 'Hello ${name}!'
}
fn main() {
msg := greet('Bob')
println(msg)
}
Greet Test
Greet Test
V has built-in testing support. Any file ending with _test.v is considered a test file. Inside test files, you write functions starting with test_ and use assert statements to check if conditions are true. You can run all tests in a folder using the v test . command.
These examples cover writing simple assertions, test suites, and testing functions that return options or errors.
Additional Context from Repository docs:
This example demonstrates the concepts of greet test.
module main
fn test_greet() {
// Arrange
name := 'Bob'
exp_msg := 'Hello Bob!'
// Act
act_msg := greet(name)
// Assert
assert act_msg == exp_msg
assert act_msg.contains(name)
}
Main Test
Main Test
V has built-in testing support. Any file ending with _test.v is considered a test file. Inside test files, you write functions starting with test_ and use assert statements to check if conditions are true. You can run all tests in a folder using the v test . command.
These examples cover writing simple assertions, test suites, and testing functions that return options or errors.
Additional Context from Repository docs:
This example demonstrates the concepts of main test.
module main
import mod1
fn test_hello() {
// arrange
exp := 'Hello from mod1!'
// act
act := mod1.hello()
// assert
assert act == exp
assert mod1.hello().contains('Hello')
}
Testing Program Modules - Helper (file1.v)
Testing Program Modules - Helper
V has built-in testing support. Any file ending with _test.v is considered a test file. Inside test files, you write functions starting with test_ and use assert statements to check if conditions are true. You can run all tests in a folder using the v test . command.
These examples cover writing simple assertions, test suites, and testing functions that return options or errors.
Additional Context from Repository docs:
This example demonstrates the concepts of file1.
module mod1
pub fn hello() string {
return 'Hello from mod1!'
}
Mod1 Test
Mod1 Test
V has built-in testing support. Any file ending with _test.v is considered a test file. Inside test files, you write functions starting with test_ and use assert statements to check if conditions are true. You can run all tests in a folder using the v test . command.
These examples cover writing simple assertions, test suites, and testing functions that return options or errors.
Additional Context from Repository docs:
This example demonstrates the concepts of mod1 test.
module mod1
fn test_hello() {
// arrange
exp := 'Hello from mod1!'
// act
act := hello()
// assert
assert act == exp
}
Modulebasics
Modulebasics
V has built-in testing support. Any file ending with _test.v is considered a test file. Inside test files, you write functions starting with test_ and use assert statements to check if conditions are true. You can run all tests in a folder using the v test . command.
These examples cover writing simple assertions, test suites, and testing functions that return options or errors.
Additional Context from Repository docs:
This example demonstrates the concepts of modulebasics.
module main
import mod1
fn main() {
res := mod1.hello()
println(res)
}
Chapter 11 Concurrency and Channels
Quick Access
Below is an index of all code examples in this chapter. You can use these links to jump directly to any specific code example:
Channels & Communication
- Unbuffered Channel
- Define Buffered Channel (buffered_channel.v)
- Push Buffered
- Push Unbuffered
- Pop
- Channel Properties
- Try Push Unbuffered
- Try Push Buffered
- Try Pop
- Close
- Defer Close
- Blocking Channels
- Dealing Before
- Dealing After
- Unbuffered Sync Before (sync_before.v)
- Unbuffered Sync After (sync_after.v)
- Understanding Buffered Channel (buffered_channel.v)
- Coroutines Communication
- Buffered Sync Before (sync_before.v)
- Buffered Sync After (sync_after.v)
- Channel Select Before
- Channel Select
V-Routines & Concurrency
- Stopwatch Demo
- Spawn Void Function
- Waiting On Concurrent Thread
- Running Multiple Tasks In Sequence
- Spawning Multiple Tasks Concurrently
- Functions With Return Values
- Spawn Anonymous Funcs Without Input Args
- Spawn Anonymous Funcs With Input Args
- Sharing Data Main And Concurrent Tasks
V makes concurrent programming easy and safe. This chapter covers spawning threads using spawn, communicating safely between threads using channels, and sharing state safely using shared and lock primitives.
Channels & Communication
Unbuffered Channel
Unbuffered Channel
Unbuffered channels in V have a capacity of 0. Sending data into an unbuffered channel blocks the sender thread until a receiver thread is ready to pop the data. This provides a strong synchronization point between execution threads.
Additional Context from Repository docs:
This example demonstrates the concepts of unbuffered channel.
module main
fn main() {
// 1. Declare an unbuffered channel of type 'int'.
// In V, channels are declared using the `chan` keyword followed by the type.
// An empty initializer `{}` defaults the capacity (`cap`) to 0.
uc := chan int{}
// 2. Query the capacity of the channel.
// For unbuffered channels, the capacity is always 0.
// This means any send operation (pushing data) will block the sending thread
// until another thread is actively reading (popping data) from the channel.
println('Unbuffered channel capacity: ${uc.cap}') // Outputs: 0
// 3. Print the type name of the channel.
// V's `typeof().name` provides runtime type reflection names.
println('Type of channel: ${typeof(uc).name}') // Outputs: chan int
}
Define Buffered Channel (buffered_channel.v)
Define Buffered Channel
Buffered channels in V are initialized with a specific capacity. The sender thread can push elements into the channel without blocking as long as the buffer is not completely full. Once the buffer is full, subsequent send operations will block until elements are read by another thread.
Additional Context from Repository docs:
This example demonstrates the concepts of buffered channel.
module main
fn main() {
// 1. Declare a buffered channel of type 'string' with a capacity of 2.
// We specify capacity using the `cap` initialization field.
bc := chan string{cap: 2}
// 2. Query the capacity of the channel.
// For buffered channels, this returns the size of the buffer.
// The sending thread will NOT block when pushing items into the channel
// until the buffer is completely full (in this case, containing 2 elements).
println('Buffered channel capacity: ${bc.cap}') // Outputs: 2
// 3. Print the type name of the channel.
println('Type of channel: ${typeof(bc).name}') // Outputs: chan string
}
Push Buffered
Push Buffered
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of push buffered.
fn main() {
ch := chan int{cap: 1}
ch <- 51
println(ch)
}
Push Unbuffered
Push Unbuffered
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of push unbuffered.
fn main() {
ch := chan int{}
ch <- 51
println(ch) // doesn't prints, due to blocking behavior of unbuffered channels
}
Pop
Pop
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of pop.
fn main() {
ch := chan int{cap: 1}
ch <- 51
println('channel after push: ${ch.str()}')
println('popping value out of the channel and storing it in immutable variable x')
x := <-ch
println('value of x: ${x}')
println('channel after pop: ${ch.str()}')
}
Deep Dive Explanation: Channels & Basic Operations
1. Unbuffered vs. Buffered Channels
- Unbuffered Channels (
cap: 0): Initialized viachan T{}. They have no intermediate storage. Any send operation (ch <- value) blocks the sender until a receiver is ready to pop the data (<-ch), and vice versa.
Deadlock Risk: In the Push Unbuffered example, calling ch <- 51 in the main thread without spawning a concurrent reader thread blocks the program permanently, resulting in a thread deadlock.
- Buffered Channels (
cap > 0): Initialized viachan T{cap: N}. They can hold up toNitems in a queue. Pushing to a buffered channel does not block as long as the current queue size is less thanN. It only blocks when the buffer is full (ch.len == N). Popping blocks only when the buffer is empty (ch.len == 0).
2. Channel Operations & Metadata Fields
- Pushing (
<-): Sends data to the channel. Format:channel_var <- data. - Popping (
<-): Receives data from the channel. Format:variable := <-channel_var. - Properties:
.cap: The static, defined capacity of the channel (0 for unbuffered)..len: The number of currently buffered elements waiting to be popped..closed: A boolean indicating if the channel has been shut down viaclose(ch).
Channel Properties
Channel Properties
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of channel properties.
fn main() {
b := chan string{cap: 2}
b <- 'hello'
println('capacity: ${b.cap}')
println('length: ${b.len}')
println('closed: ${b.closed}')
}
Try Push Unbuffered
Try Push Unbuffered
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of try push unbuffered.
fn main() {
v := 'hi'
ch := chan string{} // unbuffered channel
res := ch.try_push(v)
println(res) // not_ready
}
Try Push Buffered
Try Push Buffered
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of try push buffered.
fn main() {
x := 'hello'
ch := chan string{cap: 2}
for {
status := ch.try_push(x)
if status == .success {
println('Channel length: ${ch.len}')
} else {
println('channel status: ${status}')
break
}
}
}
Try Pop
Try Pop
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of try pop.
fn main() {
ch := chan int{cap: 1}
mut x, mut y := 0, 0
ch <- 101
mut status := ch.try_pop(mut x)
println('try pop resulted in status: ${status}, Value of x: ${x}')
status = ch.try_pop(mut y)
println('try pop resulted in status: ${status}, Value of y: ${y}')
}
Close
Close
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of close.
module main
fn main() {
ch := chan int{cap: 2}
// push using arrow operator: <-
ch <- 123 // Push 1st element into the channel
ch <- 222 // Push 2nd element into the channel
println(<-ch) // pop using: <- First in is the first to out. So prints 123
ch.close() // Close channel
// try_push will result .closed
new_val := 999
status := ch.try_push(new_val)
println('try_push on a closed channel resulted in status: ${status}')
// We still have one more element to pop
println(<-ch) // 222
}
Defer Close
Defer Close
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of defer close.
module main
fn main() {
ch := chan int{cap: 2}
defer {
ch.close()
} // Deferred execution to Close channel
// push using arrow operator: <-
ch <- 123 // Push 1st element into the channel
ch <- 222 // Push 2nd element into the channel
println(<-ch) // pop using: <- First in is the first to out. So prints 123
// try_push will result .closed
new_val := 999
status := ch.try_push(new_val)
println('try_push on a closed channel resulted in status: ${status}')
// We still have one more element to pop
println(<-ch) // 222
}
Blocking Channels
Blocking Channels
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of blocking channels.
module main
fn main() {
ch := chan int{}
defer {
ch.close()
}
ch <- 3
x := <-ch
println(x)
println('End main')
}
Dealing Before
Dealing Before
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of dealing before.
module main
fn receiver(ch chan int) {
println('Received value from the channel ${<-ch}')
}
fn main() {
ch := chan int{}
defer {
ch.close()
}
go receiver(ch)
ch <- 3
println('End main')
}
Dealing After
Dealing After
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of dealing after.
module main
fn receiver(ch chan int) {
println('Received value from the channel ${<-ch}')
}
fn main() {
ch := chan int{}
defer {
ch.close()
}
t := go receiver(ch)
ch <- 3
t.wait()
println('End main')
}
Unbuffered Sync Before (sync_before.v)
Unbuffered Sync Before
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of sync before.
module main
const count = 4
fn sender(ch chan int) {
for i in 0 .. count {
ch <- i // since the push operation is a void expression, this cannot be placed in a println
println('Sent ${i} into the channel')
}
}
fn receiver(ch chan int) {
println('Received value from the channel ${<-ch}')
}
fn main() {
ch := chan int{}
defer {
ch.close()
}
t := go receiver(ch)
go sender(ch)
t.wait()
println('End main')
}
Unbuffered Sync After (sync_after.v)
Unbuffered Sync After
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of sync after.
module main
const count = 4
fn sender(ch chan int) {
for i in 0 .. count {
ch <- i // since the push operation is a void expression, this cannot be placed in a println
println('Sent ${i} into the channel')
}
}
fn receiver(ch chan int) {
for _ in 0 .. count {
println('Received value from the channel ${<-ch}')
}
}
fn main() {
ch := chan int{}
defer {
ch.close()
}
t := go receiver(ch)
go sender(ch)
t.wait()
println('End main')
}
Understanding Buffered Channel (buffered_channel.v)
Understanding Buffered Channel
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of buffered channel.
module main
fn main() {
ch := chan int{cap: 1}
defer {
ch.close()
}
ch <- 3
x := <-ch
println(x)
println('End main')
}
Coroutines Communication
Coroutines Communication
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of coroutines communication.
module main
fn sender(ch chan int) {
val := 3
println('Sending value: ${val} in the channel')
ch <- val
println('sent value: ${val} in the channel')
}
fn receiver(ch chan int) {
println('Received value from the channel ${<-ch}')
}
fn main() {
ch := chan int{cap: 1}
defer {
ch.close()
}
t := go receiver(ch)
go sender(ch)
t.wait()
println('End main')
}
Buffered Sync Before (sync_before.v)
Buffered Sync Before
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of sync before.
module main
const count = 4
fn sender(ch chan int) {
for i in 0 .. count {
ch <- i
println('sent value: ${i} in the channel')
}
}
fn receiver(ch chan int) {
println('Received value from the channel ${<-ch}')
}
fn main() {
ch := chan int{cap: 2}
defer {
ch.close()
}
t := go receiver(ch)
go sender(ch)
t.wait()
println('End main')
}
Buffered Sync After (sync_after.v)
Buffered Sync After
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of sync after.
module main
const count = 4
fn sender(ch chan int) {
for i in 0 .. count {
ch <- i
println('sent value: ${i} in the channel')
}
}
fn receiver(ch chan int) {
for _ in 0 .. count {
println('Received value from the channel ${<-ch}')
}
}
fn main() {
ch := chan int{cap: 2}
defer {
ch.close()
}
t := go receiver(ch)
go sender(ch)
t.wait()
println('End main')
}
Channel Select Before
Channel Select Before
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of channel select before.
module main
fn process1(ch chan int) {
for i in 1 .. 6 {
sq := i * i
println('process1: value being pushed on ch1: ${sq}')
ch <- sq
}
}
fn process2(ch chan string) {
msg := 'hello from process 2'
println('process2: value being pushed on ch2: ${msg}')
ch <- msg
}
fn main() {
ch1 := chan int{cap: 5} // buffered channel
ch2 := chan string{} // unbuffered channel
defer {
ch1.close()
ch2.close()
}
go process1(ch1)
go process2(ch2)
select {
a := <-ch1 {
println('main: value popped from ch1: ${a}')
}
b := <-ch2 {
println('main: value popped from ch2: ${b}')
}
}
}
Channel Select
Channel Select
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of channel select.
module main
import time
fn process1(ch chan int) {
for i in 1 .. 6 {
sq := i * i
time.sleep(3 * time.second)
println('process1: value being pushed on ch1: ${sq}')
ch <- sq
}
}
fn process2(ch chan string) {
msg := 'hello from process 2'
println('process2: value being pushed on ch2: ${msg}')
ch <- msg
}
fn main() {
ch1 := chan int{cap: 5} // buffered channel
ch2 := chan string{} // unbuffered channel
defer {
ch1.close()
ch2.close()
}
go process1(ch1)
go process2(ch2)
mut sec := 0
for {
select {
a := <-ch1 {
sec = 0
println('main: value popped from ch1: ${a}')
}
b := <-ch2 {
sec = 0
println('main: value popped from ch2: ${b}')
}
2 * time.second {
// this case executes for every 2 seconds of inactivity by any other channels in this select statement
sec = sec + 2
println('main: more than ${sec}s passed without a channel being ready')
if sec >= 6 {
println('exiting out of select after ${sec} seconds of inactivity amongst channels')
break
}
}
}
}
println('done')
}
Deep Dive Explanation: Advanced Channel Operations & Multiplexing
1. Non-Blocking Senders & Receivers (`try_push` and `try_pop`)
If your program cannot afford to block (e.g., in high-frequency game loops or real-time networking threads), V provides non-blocking channel API methods:
try_push(val): Attempts to sendvalimmediately. It returns aChanStatusenum:.success: The value was written to the channel's buffer..not_ready: The operation would block (either because the channel is unbuffered with no active reader, or the buffered channel is full)..closed: The channel was closed, and writing is invalid.try_pop(&mut_var): Attempts to read immediately. It takes a reference to a mutable variable where the received value will be stored and returns aChanStatusenum.
2. Channel Multiplexing with `select`
V's select statement monitors multiple channel operations simultaneously:
- Blocking Mode: The
selectblock will halt execution until at least one of the specified channel reads or writes is ready. When a case is ready, its block executes, and control leaves theselectblock. - Inactivity Timeouts: You can declare a timeout block (e.g.,
2 * time.second { ... }). If all other monitored channels remain inactive for that duration, this case triggers. This is highly useful for implementing idle detection or connection timeouts. - Non-Blocking Mode (
else): Adding anelse { ... }block to aselectstatement makes it entirely non-blocking. If no channels are immediately ready, theelseblock executes instantly.
V-Routines & Concurrency
Stopwatch Demo
Stopwatch Demo
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of stopwatch demo.
module main
import time
fn main() {
sw := time.new_stopwatch()
for i in 1 .. 5 {
println('${i}')
}
println('Total time took to finish: ${sw.elapsed().seconds()} seconds')
}
Spawn Void Function
Spawn Void Function
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of spawn void function.
module main
fn greet() {
println('Hello from other side!')
}
fn main() {
h := go greet()
println(typeof(h).name) // thread
}
Waiting On Concurrent Thread
Waiting On Concurrent Thread
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of waiting on concurrent thread.
module main
fn greet() {
println('Hello from other side!')
}
fn main() {
h := go greet()
println(typeof(h).name)
h.wait()
}
Running Multiple Tasks In Sequence
Running Multiple Tasks In Sequence
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of running multiple tasks in sequence.
module main
import time
fn hot_water() {
println('Started Switch on Water heater: ${time.now().hhmmss()}')
time.sleep(5 * time.second)
println('Water heater indicates hot water ready!: ${time.now().hhmmss()}')
}
fn brush_teeth() {
println('Started brushing: ${time.now().hhmmss()}')
time.sleep(3 * time.second)
println('End Brushing: ${time.now().hhmmss()}')
}
fn select_clothes() {
println('Started choosing pair of clothes : ${time.now().hhmmss()}')
time.sleep(3 * time.second)
println('End choosing pair of clothes: ${time.now().hhmmss()}')
}
fn main() {
sw := time.new_stopwatch()
hot_water()
brush_teeth()
select_clothes()
println('Your pre bath morning chores took: ${sw.elapsed().seconds()} seconds')
}
Spawning Multiple Tasks Concurrently
Spawning Multiple Tasks Concurrently
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of spawning multiple tasks concurrently.
module main
import time
fn hot_water() {
println('Started Switch on Water heater: ${time.now().hhmmss()}')
time.sleep(5 * time.second)
println('Water heater indicates hot water ready! : ${time.now().hhmmss()}')
}
fn brush_teeth() {
println('Started brushing: ${time.now().hhmmss()}')
time.sleep(3 * time.second)
println('End Brushing: ${time.now().hhmmss()}')
}
fn select_clothes() {
println('Started choosing pair of clothes: ${time.now().hhmmss()}')
time.sleep(3 * time.second)
println('End choosing pair of clothes: ${time.now().hhmmss()}')
}
fn main() {
mut t := []thread{}
sw := time.new_stopwatch()
t << go hot_water()
t << go brush_teeth()
t << go select_clothes()
t.wait()
println('Your pre bath morning chores took: ${sw.elapsed().seconds()} seconds')
}
Functions With Return Values
Functions With Return Values
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of functions with return values.
module main
import time
fn hot_water() string {
println('Started Switch on Water heater: ${time.now().hhmmss()}')
time.sleep(5 * time.second)
println('Water heater indicates hot water ready! : ${time.now().hhmmss()}')
return 'Hot water ready!'
}
fn brush_teeth() string {
println('Started brushing: ${time.now().hhmmss()}')
time.sleep(3 * time.second)
println('End Brushing: ${time.now().hhmmss()}')
return 'Sparkling Teeth ready!'
}
fn select_clothes() string {
println('Started choosing pair of clothes: ${time.now().hhmmss()}')
time.sleep(3 * time.second)
println('End choosing pair of clothes: ${time.now().hhmmss()}')
return 'Pair of clothes ready!'
}
fn main() {
mut t := []thread string{}
sw := time.new_stopwatch()
t << go hot_water()
t << go brush_teeth()
t << go select_clothes()
res := t.wait()
println('Your pre bath morning chores took: ${sw.elapsed().seconds()} seconds')
println('*** Type Check ***')
println('Type of thread array of strings t: ${typeof(t).name}')
println('Type of res: ${typeof(res).name}')
println('*** Values returned by concurrently executed tasks ***')
println(res)
}
Spawn Anonymous Funcs Without Input Args
Spawn Anonymous Funcs Without Input Args
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of spawn anonymous funcs without input args.
module main
fn main() {
t := go fn () string {
return 'hi'
}()
x := t.wait()
println(typeof(x).name) // string
println(x) // hi
}
Spawn Anonymous Funcs With Input Args
Spawn Anonymous Funcs With Input Args
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of spawn anonymous funcs with input args.
module main
fn main() {
mut t := []thread string{}
for i in 1 .. 3 {
t << go fn (i int, msg string) string {
return 'iteration: ${i}, message: ${msg}'
}(i, 'hello') // <- arguments must match list in the anonymous function definition
}
res := t.wait()
println('Type of t: ${typeof(t).name}')
println('Type of res: ${typeof(res).name}')
println(res)
}
Sharing Data Main And Concurrent Tasks
Sharing Data Main And Concurrent Tasks
In addition to channels, V supports shared-memory concurrency using the shared keyword. Multiple threads can safely read and write to the same struct using lock (exclusive write lock) and rlock (shared read lock) blocks. This prevents race conditions and ensures synchronization without manual mutex management.
Additional Context from Repository docs:
This example demonstrates the concepts of sharing data main and concurrent tasks.
module main
import rand
// 1. Define a shared struct type.
// Structs that are intended to be shared across multiple threads should be defined normally.
// Mutability of fields is indicated as usual (e.g., total and num_donors under mut:).
struct Fund {
name string
target f32
mut:
total f32
num_donors int
}
// 2. Define a method on a shared receiver.
// In V, a receiver can be marked as 'shared' to indicate that the struct instance
// passed to it will be accessed concurrently.
fn (shared f Fund) collect(amt f32) {
// 3. Acquire a write lock.
// The `lock` block ensures exclusive (read-write) access to the shared object 'f'.
// Only one thread can execute within the lock block at a time. Other threads attempting
// to lock 'f' will block until this block exits.
lock f {
if f.total < f.target {
f.num_donors += 1
f.total += amt
// We can safely read and write to the struct fields inside the lock block.
println('${f.num_donors} \t before: ${f.total - amt} \t funds received: ${amt} \t total: ${f.total}')
}
}
}
// donation simulates generating a random donation amount.
fn donation() f32 {
// rand.f32_in_range returns a result/option type, so we use `or` to handle default.
return rand.f32_in_range(100.00, 250.00) or { 100.00 }
}
fn main() {
// 4. Declare a shared variable.
// The `shared` keyword before the variable name makes it a shared object.
// Under the hood, V automatically associates a mutex with this object.
shared fund := Fund{
name: 'A noble cause'
target: 1000.00
}
for {
// 5. Acquire a read lock (rlock).
// A read lock allows multiple threads to read the shared object concurrently
// but prevents any thread from writing to it.
rlock fund {
if fund.total >= fund.target {
break
}
}
// 6. Spawn concurrent tasks.
// The `go` keyword (interchangeable with `spawn`) starts a function in a new thread.
// `go donation()` returns a thread handle `h`.
h := go donation()
// Spawning `fund.collect` concurrently and passing the result of `h.wait()`.
// `h.wait()` blocks the main loop until the `donation()` thread finishes and returns its f32 value.
go fund.collect(h.wait())
}
// 7. Final output with read lock.
rlock fund {
println('${fund.num_donors} donors donated for ${fund.name}')
println('${fund.name} raised total fund amount: \$ ${fund.total}')
}
}
Deep Dive Explanation: V-Routines & Shared Memory Concurrency
1. Coroutines (V-Routines) via `spawn` and `go`
V supports lightweight concurrency using v-routines, which are spawned using the spawn keyword (the go keyword acts as an alias).
- When you call
spawn task(), V runs the function concurrently. - V's runtime schedules these v-routines across an OS thread pool, making them highly efficient and lightweight.
2. Thread Handles & Blocking on `.wait()`
Every spawn operation returns a thread handle:
- If the function returns a value of type
T, the handle has the typethread T. - If the function does not return a value (void), the handle is of type
thread. - Calling
.wait()on a thread handle (e.g.,result := handle.wait()) blocks the calling thread until the spawned routine completes, retrieving its return value. - You can manage multiple threads by pushing their handles into an array and waiting on all of them at once:
mut threads := []thread int{}
threads << spawn worker(1)
threads << spawn worker(2)
results := threads.wait() // Returns []int containing results from all workers
3. Shared State Mutexes (`shared`)
For shared-memory concurrency, V does not allow raw, unsynchronized access to global or heap variables across threads. Instead, variables must be explicitly marked as shared:
shared fund := Fund{...}instructs the compiler to automatically associate a mutex with thefundinstance.- Struct methods can accept a shared receiver (e.g.,
fn (shared f Fund) collect(...)).
4. Safe Synchronization: `lock` and `rlock` Blocks
To prevent data races, V enforces a strict compile-time lock check. You cannot access or modify a shared variable's fields directly. You must wrap the access in a lock block:
lock variable { ... }: Acquires an exclusive read-write lock. Use this block whenever you mutate fields of the shared structure. Only one thread can hold this lock at a time.rlock variable { ... }: Acquires a shared read-only lock. Multiple threads can enter anrlockblock concurrently to read fields, but any thread attempting to acquire alockwill be blocked until all readers exit.
Chapter 12 Working with Databases and JSON
Quick Access
Below is an index of all code examples in this chapter. You can use these links to jump directly to any specific code example:
Case Study: Notes API
JSON & ORM
- Decode
- Encode
- Json To From File
- Json Array Of Objects
- Json Map To From File
- Json Array To From File
- Orm Demo
SQLite Integration
Sqlite Raw Crud
Most applications need to work with databases or API payloads. This chapter teaches you how to serialize and deserialize JSON data, use V's built-in ORM with SQLite, and covers a complete Notes REST API case study.
Case Study: Notes API
Notes API Case Study - Main (main.v)
Notes API Case Study - Main
This is a complete, real-world case study of a REST API built using the V web framework (veb). It includes routing, JSON requests/responses, and persistence using SQLite. It is a great example of how all the pieces of V fit together to build a production-grade application.
Additional Context from Repository docs:
This example demonstrates the concepts of main.
module main
import veb
import db.sqlite
struct App {
mut:
db sqlite.DB
}
struct Context {
veb.Context
}
fn main() {
mut db := sqlite.connect('notes.db') or { panic(err) }
defer {
db.close() or {}
}
db.exec('drop table if exists Notes') or { panic(err) }
sql db {
create table Note
} or { panic(err) }
http_port := 8000
mut app := &App{
db: db
}
veb.run[App, Context](mut app, http_port)
}
Note
Note
This is a complete, real-world case study of a REST API built using the V web framework (veb). It includes routing, JSON requests/responses, and persistence using SQLite. It is a great example of how all the pieces of V fit together to build a production-grade application.
Additional Context from Repository docs:
This example demonstrates the concepts of note.
module main
import json
import veb
@[table: 'Notes']
struct Note {
id int @[primary; sql: serial]
message string @[sql: 'detail'; unique]
status bool
}
fn (n Note) to_json() string {
return json.encode(n)
}
@['/notes'; post]
fn (mut app App) create(mut ctx Context) veb.Result {
// malformed json
n := json.decode(Note, ctx.req.data) or {
ctx.res.set_status(.bad_request)
return ctx.json(error_response(400, invalid_json))
}
// before we save, we must ensure the note's message is unique
notes_found := sql app.db {
select from Note where message == n.message
} or {
ctx.res.set_status(.internal_server_error)
return ctx.json(error_response(500, err.msg()))
}
if notes_found.len > 0 {
ctx.res.set_status(.bad_request)
return ctx.json(error_response(400, unique_message))
}
// save to db
sql app.db {
insert n into Note
} or {
ctx.res.set_status(.internal_server_error)
return ctx.json(error_response(500, err.msg()))
}
// retrieve the last id from the db to build full Note object
new_id := app.db.last_id() as int
// build new note object including the new_id and send it as JSON response
note_created := Note{new_id, n.message, n.status}
ctx.res.set_status(.created)
ctx.res.header.add(.content_location, '/notes/${new_id}')
return ctx.json(note_created.to_json())
}
@['/notes/:id'; get]
fn (mut app App) read(mut ctx Context, id int) veb.Result {
n := sql app.db {
select from Note where id == id
} or {
ctx.res.set_status(.internal_server_error)
return ctx.json(error_response(500, err.msg()))
}
// check if note exists
if n.len == 0 {
ctx.res.set_status(.not_found)
return ctx.json(error_response(400, note_not_found))
}
// found note, return it
ret := json.encode(n[0])
ctx.res.set_status(.ok)
return ctx.json(ret)
}
@['/notes'; get]
fn (mut app App) read_all(mut ctx Context) veb.Result {
n := sql app.db {
select from Note
} or {
ctx.res.set_status(.internal_server_error)
return ctx.json(error_response(500, err.msg()))
}
ret := json.encode(n)
ctx.res.set_status(.ok)
return ctx.json(ret)
}
@['/notes/:id'; put]
fn (mut app App) update(mut ctx Context, id int) veb.Result {
// malformed json
n := json.decode(Note, ctx.req.data) or {
ctx.res.set_status(.bad_request)
return ctx.json(error_response(400, invalid_json))
}
// check if note to be updated exists
note_to_update := sql app.db {
select from Note where id == id
} or {
ctx.res.set_status(.internal_server_error)
return ctx.json(error_response(500, err.msg()))
}
if note_to_update.len == 0 {
ctx.res.set_status(.not_found)
return ctx.json(error_response(404, note_not_found))
}
// before update, we must ensure the note's message is unique
// id != id for idempotency
// message == n.message for unique check
res := sql app.db {
select from Note where message == n.message && id != id
} or {
ctx.res.set_status(.internal_server_error)
return ctx.json(error_response(500, err.msg()))
}
if res.len > 0 {
ctx.res.set_status(.bad_request)
return ctx.json(error_response(400, unique_message))
}
// update the note
sql app.db {
update Note set message = n.message, status = n.status where id == id
} or {
ctx.res.set_status(.internal_server_error)
return ctx.json(error_response(500, err.msg()))
}
// build the updated note using the :id and request body
// instead of making one more db call
updated_note := Note{id, n.message, n.status}
ret := json.encode(updated_note)
ctx.res.set_status(.ok)
return ctx.json(ret)
}
@['/notes/:id'; delete]
fn (mut app App) delete(mut ctx Context, id int) veb.Result {
sql app.db {
delete from Note where id == id
} or {
ctx.res.set_status(.internal_server_error)
return ctx.json(error_response(500, err.msg()))
}
ctx.res.set_status(.no_content)
return ctx.ok('')
}
Util
Util
This is a complete, real-world case study of a REST API built using the V web framework (veb). It includes routing, JSON requests/responses, and persistence using SQLite. It is a great example of how all the pieces of V fit together to build a production-grade application.
Additional Context from Repository docs:
This example demonstrates the concepts of util.
module main
import json
struct NotesResponse {
status int
message string
}
fn (c NotesResponse) to_json() string {
return json.encode(c)
}
const invalid_json = 'Invalid JSON Payload'
const note_not_found = 'Note not found'
const unique_message = 'Please provide a unique message for Note'
fn error_response(status int, message string) string {
er := NotesResponse{status, message}
return er.to_json()
}
JSON & ORM
Decode
Decode
Databases and JSON handling are essential parts of backend development. This lesson on Decode details V's built-in JSON utilities or its built-in database ORM.
Additional Context from Repository docs:
This example demonstrates the concepts of decode.
import json
struct Note {
id int
message string
status bool
}
fn main() {
// Decode a JSON payload into a struct instance.
n := json.decode(Note, '{"id":1,"message":"Plan a holiday","status":false}') or {
panic('invalid json data')
}
// Print the type name and the decoded data for inspection.
println(typeof(n).name) // Note
println(n)
}
Encode
Encode
Databases and JSON handling are essential parts of backend development. This lesson on Encode details V's built-in JSON utilities or its built-in database ORM.
Additional Context from Repository docs:
This example demonstrates the concepts of encode.
import json
struct Note {
id int
message string
status bool
}
fn main() {
// Create a note object that will be converted to JSON.
m := Note{
id: 2
message: 'Get groceries'
status: false
}
// Encode the struct to a compact JSON string.
mut j := json.encode(m)
println(j)
// Encode the same object with pretty formatting for readability.
j = json.encode_pretty(m)
println(j)
}
Deep Dive Explanation: JSON Serialization & Deserialization
1. Compile-Time JSON Parsing
Unlike many languages that rely on slow, runtime reflection to inspect structures, V's compiler generates encoding and decoding code statically at compile time. This ensures extremely fast performance and safety.
2. Decoding JSON (`json.decode`)
- To decode a JSON string, invoke
json.decode(StructName, json_string). - Result Type Return: Since incoming JSON strings can be malformed,
json.decodereturns a Result type (!StructName). You must unwrap it with anorblock:
user := json.decode(User, raw_json) or {
println('Failed to parse user JSON: ${err}')
return
}
3. Encoding to JSON (`json.encode`)
- To serialize a V struct instance into a JSON string, invoke
json.encode(instance). - This operation is guaranteed to succeed and returns a standard
stringdirectly (noorblock required).
4. Struct JSON Attribute Tags
V provides structural attributes to customize JSON mapping. These are written inside @[...] brackets placed on the same line as the field:
- Custom Naming:
field string @[json: 'custom_name']maps the struct field to the'custom_name'JSON key. - Skip Fields:
secret string @[json: '-']prevents the field from being serialized or deserialized. - Required Keys:
id int @[required]ensures that if theidkey is missing in the JSON payload, the decoder returns an error.
Json To From File
This example demonstrates how to encode an object to JSON, write it to a file, read it back, and decode it into a V struct.
module main
import json
import os
struct Book {
title string
author string
year int
}
fn main() {
file_path := 'book.json'
// Create an object instance
book := Book{
title: 'The V Programming Language'
author: 'Alex Medvednikov'
year: 2019
}
// 1. Encode object to JSON string
println('Encoding object to JSON...')
json_str := json.encode(book)
println('JSON string: ${json_str}')
// 2. Write JSON string to file
println('Writing JSON to file "${file_path}"...')
os.write_file(file_path, json_str) or {
eprintln('Failed to write file: ${err}')
return
}
// 3. Read JSON string from file
println('Reading JSON from file "${file_path}"...')
content := os.read_file(file_path) or {
eprintln('Failed to read file: ${err}')
return
}
// 4. Decode JSON string back to Book object
println('Decoding JSON back to object...')
decoded_book := json.decode(Book, content) or {
eprintln('Failed to decode JSON: ${err}')
return
}
println('Decoded book: Title: "${decoded_book.title}", Author: "${decoded_book.author}", Year: ${decoded_book.year}')
// Clean up created file
os.rm(file_path) or {}
}
Json Array Of Objects
This example demonstrates how to serialize and deserialize an array of objects (structs) to and from JSON, and how to write/read them using the filesystem.
module main
import json
import os
struct Task {
id int
title string
done bool
}
fn main() {
file_path := 'tasks.json'
// Create an array of objects
tasks := [
Task{
id: 1
title: 'Read V Guide'
done: false
},
Task{
id: 2
title: 'Write JSON helper examples'
done: true
},
]
// 1. Encode array of objects to JSON string
println('Encoding array of objects to JSON...')
json_str := json.encode(tasks)
println('JSON string:\n${json_str}')
// 2. Write JSON string to file
println('\nWriting JSON array to file "${file_path}"...')
os.write_file(file_path, json_str) or {
eprintln('Failed to write file: ${err}')
return
}
// 3. Read JSON string from file
println('Reading JSON from file "${file_path}"...')
content := os.read_file(file_path) or {
eprintln('Failed to read file: ${err}')
return
}
// 4. Decode JSON string back to an array of Task objects
println('Decoding JSON back to array of objects...')
decoded_tasks := json.decode([]Task, content) or {
eprintln('Failed to decode JSON: ${err}')
return
}
println('Decoded array of tasks successfully!')
for task in decoded_tasks {
println(' - Task #${task.id}: "${task.title}" [Done: ${task.done}]')
}
// Clean up created file
os.rm(file_path) or {}
}
Json Map To From File
This example demonstrates how to serialize a map structure (map[string]int) into a JSON string, write it to a file, read it back, and deserialize it back into a map in V.
module main
import json
import os
fn main() {
file_path := 'scores.json'
// Create a map[string]int
scores := {
'Alice': 95
'Bob': 88
'Charlie': 92
}
// 1. Encode map to JSON string
println('Encoding map to JSON...')
json_str := json.encode(scores)
println('JSON string: ${json_str}')
// 2. Write JSON string to file
println('Writing map JSON to file "${file_path}"...')
os.write_file(file_path, json_str) or {
eprintln('Failed to write file: ${err}')
return
}
// 3. Read JSON string from file
println('Reading from file "${file_path}"...')
content := os.read_file(file_path) or {
eprintln('Failed to read file: ${err}')
return
}
// 4. Decode JSON string back to map[string]int
println('Decoding JSON back to map...')
decoded_scores := json.decode(map[string]int, content) or {
eprintln('Failed to decode map JSON: ${err}')
return
}
println('Decoded map successfully:')
for k, v in decoded_scores {
println(' - ${k}: ${v}')
}
// Clean up created file
os.rm(file_path) or {}
}
Json Array To From File
This example demonstrates two different methods for reading and writing arrays to/from files in V:
- JSON Serialization: Best for primitive numeric/boolean arrays (e.g.
[]int). - Raw Line-by-Line (Plain Text): Best for string lists (e.g.
[]string), joining with newlines on write and using V's standardos.read_lines()on read.
module main
import json
import os
fn main() {
// We will show two ways of writing/reading arrays to/from files:
// Method 1: Using JSON serialization (great for numeric or structured arrays)
// Method 2: Using raw text line-by-line reading/writing (great for string lists)
// --- Method 1: JSON Serialization ---
println('=== Method 1: JSON Serialization ===')
json_file_path := 'numbers.json'
numbers := [10, 20, 30, 40, 50]
println('Encoding array to JSON...')
json_str := json.encode(numbers)
println('JSON string: ${json_str}')
println('Writing JSON to file "${json_file_path}"...')
os.write_file(json_file_path, json_str) or {
eprintln('Failed to write file: ${err}')
return
}
json_content := os.read_file(json_file_path) or {
eprintln('Failed to read file: ${err}')
return
}
decoded_numbers := json.decode([]int, json_content) or {
eprintln('Failed to decode array JSON: ${err}')
return
}
println('Decoded array: ${decoded_numbers}')
os.rm(json_file_path) or {}
// --- Method 2: Raw Line-by-Line (Plain Text) ---
println('\n=== Method 2: Raw Line-by-Line ===')
text_file_path := 'fruits.txt'
fruits := ['Apple', 'Banana', 'Cherry', 'Date']
println('Writing array elements to text file "${text_file_path}"...')
// Join the string array with newlines to write line-by-line
fruits_content := fruits.join('\n')
os.write_file(text_file_path, fruits_content) or {
eprintln('Failed to write file: ${err}')
return
}
println('Reading lines from file "${text_file_path}" using os.read_lines()...')
// os.read_lines reads a file directly into a []string (line by line)
read_fruits := os.read_lines(text_file_path) or {
eprintln('Failed to read lines: ${err}')
return
}
println('Read string array: ${read_fruits}')
// Clean up created files
os.rm(text_file_path) or {}
}
Orm Demo
Orm Demo
Databases and JSON handling are essential parts of backend development. This lesson on Orm Demo details V's built-in JSON utilities or its built-in database ORM.
Additional Context from Repository docs:
This example demonstrates the concepts of orm demo.
module main
import db.sqlite
@[table: 'Notes']
struct Note {
id int @[primary; sql: serial]
message string @[sql: 'detail'; unique]
status bool
}
fn main() {
// Establishing a connection to the database
mut db := sqlite.connect('NotesDB.db') or { panic(err) }
defer {
db.close() or {}
}
db.exec('drop table if exists Notes') or { panic(err) }
// Creating a table
sql db {
create table Note
} or { panic(err) }
// Inserting record(s)
n1 := Note{
message: 'Get some milk'
status: false
}
n2 := Note{
message: 'Get groceries'
status: false
}
sql db {
insert n1 into Note
insert n2 into Note
} or { panic(err) }
println(db.last_id() as int)
// Select records
all_notes := sql db {
select from Note
} or { panic(err) }
println(all_notes)
println('Type of all_notes is : ${typeof(all_notes).name}')
// Select using order by clause
notes_sorted := sql db {
select from Note order by id desc
} or { panic(err) }
println(notes_sorted)
// Select using the limit clause
notes_limited := sql db {
select from Note order by id desc limit 1
} or { panic(err) }
println(notes_limited)
println('Type returned by select when limit is 1: ${typeof(notes_limited).name}')
// Select using where clause
notes_latest := sql db {
select from Note where id > 1
} or { panic(err) }
println(notes_latest)
// Update record(s)
sql db {
update Note set status = true where id == 2
} or { panic(err) }
notes_updated := sql db {
select from Note where id == 2
} or { panic(err) }
println(notes_updated)
// Delete record(s)
sql db {
delete from Note where id == 2
} or { panic(err) }
notes_leftover := sql db {
select from Note
} or { panic(err) }
println(notes_leftover)
sql db {
drop table Note
} or { panic(err) }
println('Dropped the Note table from database!')
}
Deep Dive Explanation: V's Compile-Safe Database ORM
1. Compile-Time Query Safety
V features a built-in ORM (supporting SQLite, PostgreSQL, and MySQL) that integrates directly with V's type system:
- The
sqlBlock: All ORM operations are written inside a specialsql db { ... }block. - Type Safety: The compiler validates table structures, column types, and query logic during compilation. For example, trying to compare a string field to an integer inside the
whereclause will fail to compile. - SQL Injection Prevention: All variables referenced in query clauses (like
where id == id_var) are automatically treated as query parameters under the hood, making V's ORM immune to SQL injection attacks out of the box.
2. Struct Attributes for ORM Schema Design
You configure your database schema by annotating V structs with attributes:
@[table: 'name']: Customizes the table name in the database (defaults to the struct name).@[primary; sql: serial]: Configures the field as an auto-incrementing primary key.@[sql: 'col_name']: Customizes the column name in the database (defaults to the field name).@[unique]: Adds a unique constraint to the column.
3. ORM Operations
- Create Table:
sql db { create table Note } or { ... }
Generates the SQL DDL statements and creates the table based on the struct fields and attributes.
- Insert Records:
sql db { insert n1 into Note } or { ... }
Inserts the struct instance into the database. If successful, V updates any auto-incrementing primary keys directly on the passed struct instance.
- Query Records (
select):
Queries return a slice of structs (e.g. []Note). Standard SQL clauses are fully supported:
where: Filter records using standard V boolean operations.order by: Sort records (e.g.,order by id desc).limit: Restrict the number of returned records.offset: Skip a number of records (used for pagination).- Update Records:
sql db { update Note set status = true where id == 2 } or { ... }
Updates the records matching the predicate.
- Delete Records:
sql db { delete from Note where id == 2 } or { ... }
Deletes the records matching the predicate.
SQLite Integration
Sqlite
Sqlite
Databases and JSON handling are essential parts of backend development. A small SQLite helper module makes your application code cleaner by centralizing connection setup, schema initialization, and CRUD operations in one place.
This example shows a practical pattern you can reuse in small tools, desktop apps, and prototypes:
- connect to a database with a single helper
- enable foreign keys for safer relations
- initialize a table with a reusable setup function
- create, read, update, and delete records with safe parameterized queries
- prevent SQL injection by using
exec_param_manyinstead of string interpolation
module sqlite
import db.sqlite as dbsqlite
pub type DB = dbsqlite.DB
pub struct Note {
id int
title string
body string
}
pub fn connect(path string) !DB {
mut db := dbsqlite.connect(path)!
db.exec('PRAGMA foreign_keys = ON;') or {
return error('failed to enable foreign keys: ${err}')
}
return db
}
pub fn connect_in_memory() !DB {
return connect(':memory:')
}
pub fn init_notes_table(mut db DB) ! {
db.exec('CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, body TEXT NOT NULL);') or {
return error('failed to create notes table: ${err}')
}
}
pub fn create_note(mut db DB, title string, body string) !int {
db.exec_param_many('INSERT INTO notes (title, body) VALUES (?, ?);', [title, body]) or {
return error('failed to insert note: ${err}')
}
return db.last_id()
}
pub fn list_notes(mut db DB) ![]Note {
rows := db.exec('SELECT id, title, body FROM notes ORDER BY id;') or {
return error('failed to list notes: ${err}')
}
mut notes := []Note{}
for row in rows {
notes << Note{
id: row.vals[0].int()
title: row.vals[1]
body: row.vals[2]
}
}
return notes
}
pub fn update_note(mut db DB, id int, title string, body string) ! {
db.exec_param_many('UPDATE notes SET title = ?, body = ? WHERE id = ?;', [title, body,
id.str()]) or { return error('failed to update note: ${err}') }
}
pub fn delete_note(mut db DB, id int) ! {
db.exec_param_many('DELETE FROM notes WHERE id = ?;', [id.str()]) or {
return error('failed to delete note: ${err}')
}
}
Safe database access: always pass user input as parameters using exec_param_many or exec with placeholders like ?. This prevents SQL injection and keeps your queries readable.
SQLite CRUD Helper
SQLite CRUD Helper
This template demonstrates a complete SQLite database workflow using raw SQL queries. It includes creating tables, clearing data, inserting records with parameterized inputs to prevent SQL injection, and fetching records into structured types.
Key concepts illustrated:
- Database Connection: Opening and closing a SQLite database using
sqlite.connect. - Schema Management: Creating tables dynamically using
db.exec. - Parameterized Queries: Preventing SQL injection by passing variables inside string arrays using
db.exec_param_many. - Result Mapping: Manually parsing returned rows into structured V structs.
- Resource Cleanup: Appending log files or temporary databases, and deleting them cleanly via
deferblocks to prevent stray files on disk.
module main
import db.sqlite
import os
struct User {
id int
name string
email string
age int
}
fn connect_db(path string) !sqlite.DB {
return sqlite.connect(path)
}
fn init_schema(mut db sqlite.DB) ! {
db.exec('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT UNIQUE, age INTEGER);') or {
return error('Could not create table: ${err}')
}
}
fn reset_users(mut db sqlite.DB) ! {
db.exec('DELETE FROM users;') or { return error('Could not clear users: ${err}') }
}
fn insert_user(mut db sqlite.DB, name string, email string, age int) !int {
db.exec_param_many('INSERT INTO users (name, email, age) VALUES (?, ?, ?);', [
name,
email,
age.str(),
]) or { return error('Insert failed: ${err}') }
return db.last_id()
}
fn fetch_users(mut db sqlite.DB) ![]User {
rows := db.exec('SELECT id, name, email, age FROM users ORDER BY id;') or {
return error('Select failed: ${err}')
}
mut users := []User{}
for row in rows {
users << User{
id: row.vals[0].int()
name: row.vals[1]
email: row.vals[2]
age: row.vals[3].int()
}
}
return users
}
// Reusable CRUD helpers for the SQLite boilerplate example.
fn main() {
println('=== V SQLite CRUD Boilerplate ===')
db_path := 'demo.db'
defer {
if os.exists(db_path) {
os.rm(db_path) or {}
println('Cleaned up temporary database: ${db_path}')
}
}
mut db := connect_db(db_path) or {
eprintln('${err}')
return
}
defer {
db.close() or { eprint('Failed to close database: ${err}') }
}
init_schema(mut db) or {
eprintln('${err}')
return
}
reset_users(mut db) or {
eprintln('${err}')
return
}
user_id := insert_user(mut db, 'Ada', 'ada@example.com', 36) or {
eprintln('${err}')
return
}
println('Inserted user id: ${user_id}')
users := fetch_users(mut db) or {
eprintln('${err}')
return
}
for user in users {
println('User: ${user.id} ${user.name} (${user.email}, age ${user.age})')
}
}
Sqlite Raw Crud
This example demonstrates how to connect to a SQLite database and execute raw SQL queries securely using parameterized queries (db.exec_param_many) to prevent SQL Injection attacks.
It illustrates:
- Connecting with
sqlite.connect. - Executing statements that do not return tabular results (like DDL/DML) via
db.exec. - Executing parameterized DML queries (INSERT, SELECT, UPDATE, DELETE) using
db.exec_param_manyand?placeholders. - Fetching result sets from
SELECTstatements into[]sqlite.Row. - Iterating and accessing row values from
row.valsby index. - Handling resource cleanup cleanly via
defer.
SQL Injection Prevention: Avoid raw string interpolation (e.g. "$name") inside raw queries. Using db.exec_param_many forces parameter binding, rendering the query secure from malicious inputs.
module main
import db.sqlite
fn main() {
// 1. Database Connection
// Connects to a SQLite database. ':memory:' creates a temporary in-memory database.
println('Connecting to database...')
mut db := sqlite.connect(':memory:') or {
println('Connection failed: ${err}')
return
}
defer {
db.close() or { println('Failed to close database: ${err}') }
println('Database connection closed.')
}
// 2. Schema Creation (DDL)
println('Creating "users" table...')
db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT UNIQUE, age INTEGER);') or {
println('Table creation failed: ${err}')
return
}
// 3. Create (Insert Records using Parameterized Queries)
println('\n--- CREATE: Inserting records securely ---')
// SAFE APPROACH: Use `exec_param_many` with '?' placeholders to prevent SQL Injection.
// Parameters are passed as an array of strings: []string
db.exec_param_many('INSERT INTO users (name, email, age) VALUES (?, ?, ?);', [
'Alice',
'alice@example.com',
'30',
]) or { println('Insert failed: ${err}') }
db.exec_param_many('INSERT INTO users (name, email, age) VALUES (?, ?, ?);', [
'Bob',
'bob@example.com',
'25',
]) or { println('Insert failed: ${err}') }
db.exec_param_many('INSERT INTO users (name, email, age) VALUES (?, ?, ?);', [
'Charlie',
'charlie@example.com',
'40',
]) or { println('Insert failed: ${err}') }
println('Last inserted row ID: ${db.last_id()}')
// 4. Read (Select Records using Parameterized Queries)
println('\n--- READ: Querying records securely ---')
// Querying with parameters: only retrieve users older than 20
rows := db.exec_param_many('SELECT id, name, email, age FROM users WHERE age > ?;',
['20']) or {
println('Select failed: ${err}')
[]sqlite.Row{}
}
// Iterate and extract column values by index
for row in rows {
// Each sqlite.Row has two string arrays: `vals` (values) and `names` (column names)
id := row.vals[0]
name := row.vals[1]
email := row.vals[2]
age := row.vals[3]
println('User [ID: ${id}] -> Name: ${name}, Email: ${email}, Age: ${age}')
}
// 5. Update (Modify Records using Parameterized Queries)
println("\n--- UPDATE: Modifying Bob's email and age securely ---")
db.exec_param_many('UPDATE users SET email = ?, age = ? WHERE name = ?;', [
'bob_new@example.com',
'26',
'Bob',
]) or { println('Update failed: ${err}') }
// Verify update
updated_rows := db.exec_param_many('SELECT email, age FROM users WHERE name = ?;',
['Bob']) or { []sqlite.Row{} }
if updated_rows.len > 0 {
println("Bob's new email: ${updated_rows[0].vals[0]}")
println("Bob's new age: ${updated_rows[0].vals[1]}")
}
// 6. Delete (Remove Records using Parameterized Queries)
println('\n--- DELETE: Removing Charlie securely ---')
db.exec_param_many('DELETE FROM users WHERE name = ?;', ['Charlie']) or {
println('Delete failed: ${err}')
}
// Verify delete
remaining_rows := db.exec('SELECT name FROM users;') or { []sqlite.Row{} }
print('Remaining users: ')
for row in remaining_rows {
print('${row.vals[0]} ')
}
println('')
// 7. Cleanup
println('\nDropping "users" table...')
db.exec('DROP TABLE users;') or { println('Drop table failed: ${err}') }
}
Chapter 13 Standard Library & Advanced Features
Quick Access
Below is an index of all code examples in this chapter. You can use these links to jump directly to any specific code example:
Inline Assembly & C Interop
Networking (TCP, UDP, SSL, WebSockets)
- Net Urllib
- Net Websocket
- Websocket Persistent
- Net Html
- Net Jsonrpc
- Net Jsonrpc Persistent
- Net Ssl
- Ssl Persistent
- Net Tcp
- Tcp Persistent
- Net Udp
- Udp Persistent
- Net Unix
- Unix Persistent
Other Stdlib Updates
- Options And Results
- Generics
- Interfaces
- Sum Types
- Attributes
- Compile-Time Directives
- Strings Builder
- Os Advanced Io
- Os Operations
- Os Process Pipe
- Os System Info
- Time And Stopwatch
- Http Client
- Regex Matching
- Command Line Flags
- Datatypes Collections
- Gg Graphics
- Command Line Arguments
- Math And Rand
- Crypto Asymmetric
- Crypto Entropy
- Crypto Hash
- Crypto Kdf
- Crypto Mac
- Crypto Symmetric
- Log And Crypto
- Sync Concurrency
- Encoding Formats
- Arrays Utility
- Toml
- Strconv
- Term
- Benchmark
- Clipboard
- Semver
- Maps Standard Library Module (maps.v)
- Context
- Archive Tar
- Compress Deflate
- Compress Gzip
- Compress Szip
- Compress Zlib
- Compress Zstd
- Io Fs
- Io
- Io Util
- Hash
- Bitfield
- Cli
- Veb
- Readline
- Runtime
Strings.Lorem Helper
WebAssembly Compilation
Language Updates & Low-Level Features
- sizeof and \_\_offsetof
- Limited Operator Overloading
- Atomics
- Static Variables
- Hot Code Reloading
- Compile-Time Reflection
- Environment-Specific Files & Compile-Time Types
- References & Pointers
- Dumping Expressions at Runtime
This chapter highlights the power of V's standard library and advanced integration features, including low-level socket networking, inline assembly, compilation to WebAssembly, and V's unique memory management models.
Inline Assembly & C Interop
Inline Assembly
V supports inline assembly block definitions using the asm keyword, allowing developers to execute architecture-specific instructions directly from V code. It integrates directly with V variables by mapping them to inputs and outputs using register constraints.
This example demonstrates how to:
- Write inline assembly blocks targeted at specific architectures (e.g.
arm64andamd64). - Utilize V's compile-time conditional block
$ifto compile architecture-specific assembly blocks. - Map V variables to assembly inputs and outputs using semicolon constraint annotations (e.g.
; +r (res)).
module main
// add_five adds 5 to the given integer using inline assembly.
// It uses compile-time conditional checks ($if) to select the correct
// assembly instructions depending on the target CPU architecture.
fn add_five(val int) int {
mut res := val
$if arm64 {
// ARM64 inline assembly for Apple Silicon (macOS M-series) and ARM Linux/Android.
// - Syntax: add destination, operand1, operand2
// - Constraints: '; +r (res)' specifies that 'res' is both an input and output register.
asm arm64 {
add res, res, 5
; +r (res)
}
} $else $if amd64 {
// AMD64 (x86_64) inline assembly for Intel/AMD processors.
// - Syntax: add destination, source
// - Constraints: '; +r (res)' specifies that 'res' is both an input and output register.
asm amd64 {
add res, 5
; +r (res)
}
} $else {
// Fallback for other architectures (e.g. 32-bit x86, RISC-V, WebAssembly, etc.)
res += 5
}
return res
}
// multiply_by_two multiplies the given integer by 2 using bit shifting in inline assembly.
fn multiply_by_two(val int) int {
mut res := val
$if arm64 {
// ARM64 inline assembly using logical shift left (lsl) by 1 bit.
asm arm64 {
lsl res, res, 1
; +r (res)
}
} $else $if amd64 {
// AMD64 inline assembly using shift arithmetic left (sal).
asm amd64 {
sal res, 1
; +r (res)
}
} $else {
res = res << 1
}
return res
}
fn main() {
println('=== V Inline Assembly (asm) Demo ===')
// Test 1: Addition using inline assembly
num := 10
num_plus_five := add_five(num)
println('Result of add_five(${num}): ${num_plus_five}')
assert num_plus_five == 15
// Test 2: Multiplication (bit-shifting) using inline assembly
val := 21
val_double := multiply_by_two(val)
println('Result of multiply_by_two(${val}): ${val_double}')
assert val_double == 42
println('All inline assembly assertions successfully verified!')
}
C Interop
C Interop
V is designed with first-class support for C integration. Since V compiles directly to C, calling C library functions, passing C structs, and compiling legacy C code alongside V is highly performant and requires no heavy wrapper generator.
This example demonstrates how to:
- Include standard C headers using
#include <header.h>. - Declare C functions using the
fn C.name(args) typesyntax. - Map C structures in V using
@[typedef] struct C.nameto represent C typedefs. - Interact with C variables, functions, and structs directly from V.
module main
// 1. Include C standard headers.
// V compiles directly to C, so we can use C preprocessor directives like `#include`
// to bring in C standard library definitions or external C headers.
#include <math.h>
#include <stdlib.h>
// 2. Declare C functions.
// We declare C functions using the `fn C.name(args) return_type` syntax.
// The V compiler translates calls to these functions directly to the native C functions.
fn C.abs(x int) int
fn C.sqrt(x f64) f64
// 3. Declare C Structs.
// V can also interact with C structs. We use `struct C.name` to define them.
// The `@[typedef]` attribute tells the V compiler that `div_t` is a typedef structure
// in C (defined in <stdlib.h>) so it does not prefix it with the `struct` keyword in the C output.
@[typedef]
struct C.div_t {
quot int
rem int
}
// Declare C.div function from stdlib.h which returns a C.div_t struct.
fn C.div(numer int, denom int) C.div_t
fn main() {
println('=== V C Interop Demo ===')
// 4. Calling C.abs
negative_val := -42
absolute_val := C.abs(negative_val)
println('C.abs(${negative_val}) = ${absolute_val}')
assert absolute_val == 42
// 5. Calling C.sqrt
float_val := 16.0
square_root := C.sqrt(float_val)
println('C.sqrt(${float_val}) = ${square_root}')
assert square_root == 4.0
// 6. Working with C Structs and functions returning C Structs
numerator := 10
denominator := 3
div_result := C.div(numerator, denominator)
println('C.div(${numerator}, ${denominator}) -> Quotient: ${div_result.quot}, Remainder: ${div_result.rem}')
assert div_result.quot == 3
assert div_result.rem == 1
println('All C Interop functions successfully executed and verified!')
}
Networking (TCP, UDP, SSL, WebSockets)
Net Urllib
Net Urllib
The net.urllib standard library module provides utilities for parsing, analyzing, and constructing Uniform Resource Locators (URLs). When working with remote HTTP APIs, you frequently need to break URLs down into constituent components (like schemes, hosts, ports, paths, credentials, and parameters) or perform URL-encoding/decoding on query strings so they are transmitted safely across network interfaces. V uses the option type pattern (or { ... }) on methods like parse to ensure that malformed URLs are caught cleanly without crashing the program.
This example illustrates parsing URLs, escaping special string characters, and creating encoded query objects.
Additional Context from Repository docs:
This example demonstrates parsing URLs into components, escaping and unescaping query parameters, and encoding query parameters using the net.urllib module.
module main
import net.urllib
fn main() {
println('=== net.urllib Module Demo ===')
// 1. Parsing a URL
raw_url := 'https://user:pass@vlang.io:8080/docs/stdlib?lang=v&version=0.5.1#intro'
println('Parsing URL: ${raw_url}')
u := urllib.parse(raw_url) or {
println('Failed to parse URL: ${err}')
return
}
println('Parsed URL parts:')
println(' Scheme: ${u.scheme}')
println(' Host: ${u.host}')
println(' Path: ${u.path}')
println(' Query: ${u.raw_query}')
println(' Fragment: ${u.fragment}')
// 2. Query escaping and unescaping
original_query := 'V compiler version 0.5.1 & details'
escaped := urllib.query_escape(original_query)
unescaped := urllib.query_unescape(escaped) or { 'failed' }
println('\nQuery Escaping:')
println(' Original: ${original_query}')
println(' Escaped: ${escaped}')
println(' Unescaped: ${unescaped}')
// 3. Managing Query Parameters using urllib.Values
println('\nManaging Query Values:')
mut query_params := urllib.new_values()
query_params.add('format', 'json')
query_params.add('tags', 'programming')
query_params.add('tags', 'tutorial')
query_params.set('version', '0.5.1')
// Encode to raw query string
encoded_query := query_params.encode()
println(' Encoded query string: ${encoded_query}')
// Parse it back
parsed_params := urllib.parse_query(encoded_query) or { urllib.new_values() }
println(' Parsed format tag: ${parsed_params.get('format') or { 'none' }}')
println(' Parsed tags: ${parsed_params.get_all('tags')}')
}
Net Websocket
Net Websocket
The net.websocket module provides robust client and server APIs for real-time bidirectional communication over WebSockets. Under the hood, V's WebSocket implementation supports both unencrypted (ws://) and encrypted (wss://) sockets, complete with connection event handling, payload framing, and callback registers. Since networking functions block execution, WebSocket servers are typically run concurrently (e.g. using V's coroutines/threads via the go keyword) to handle incoming messages in the background while keeping the main loop responsive.
This example demonstrates spinning up a local WebSocket server, connecting a WebSocket client to it, exchanging messages, and closing the connection cleanly.
Additional Context from Repository docs:
This example demonstrates spinning up a local WebSocket server, connecting a WebSocket client to it, exchanging messages, and closing the connection cleanly using the net.websocket module.
module main
import net.websocket
import time
fn main() {
println('=== net.websocket Module Demo ===')
port := 30099
uri := 'ws://localhost:${port}'
// 1. Initialize and run a local WebSocket server in a separate thread
mut ws_server := websocket.new_server(.ip, port, '/')
ws_server.on_connect(fn (mut s websocket.ServerClient) !bool {
println('Server: Client connecting from ${s.client_key}')
return true
})!
ws_server.on_message(fn (mut ws websocket.Client, msg &websocket.Message) ! {
if msg.opcode == .text_frame {
payload := msg.payload.bytestr()
println('Server received text: "${payload}"')
// Echo message back to client
ws.write_string('Echo: ' + payload)!
}
})
// Run the server listening loop in a background thread
spawn fn [mut ws_server] () {
ws_server.listen() or { println('Server error: ${err}') }
}()
// Allow the server a moment to start
time.sleep(100 * time.millisecond)
// 2. Initialize the WebSocket client
mut ws_client := websocket.new_client(uri) or {
println('Client init failed: ${err}')
return
}
ws_client.on_open(fn (mut c websocket.Client) ! {
println('Client: Connection opened!')
})
ws_client.on_message(fn (mut c websocket.Client, msg &websocket.Message) ! {
if msg.opcode == .text_frame {
payload := msg.payload.bytestr()
println('Client received text response: "${payload}"')
}
})
ws_client.on_error(fn (mut c websocket.Client, error_msg string) ! {
println('Client error: ${error_msg}')
})
// 3. Connect and run the client
ws_client.connect() or {
println('Client failed to connect: ${err}')
return
}
// Start the client listen loop in a background thread
spawn ws_client.listen()
// 4. Send a test message
time.sleep(50 * time.millisecond)
msg_to_send := 'Hello WebSocket Server!'
println('Client sending: "${msg_to_send}"')
ws_client.write_string(msg_to_send) or { println('Client failed to send: ${err}') }
// Wait for echo to arrive
time.sleep(200 * time.millisecond)
// Clean close
println('Client closing connection...')
ws_client.close(1000, 'Done') or { println('Client close error: ${err}') }
time.sleep(50 * time.millisecond)
println('WebSocket Demo finished.')
}
Websocket Persistent
This example demonstrates a persistent WebSocket connection with structured JSON message routing, validation of payload size, and connection closure on limit violation.
module main
import net.websocket
import time
import json
// WsMessage represents a structured application-level WebSocket message.
struct WsMessage {
pub:
action string
data string
}
// ClientState maintains state for the WebSocket client across callbacks.
struct ClientState {
mut:
count int
}
fn main() {
println('=== Persistent WebSocket Protocol Demo ===')
port := 38292
uri := 'ws://localhost:${port}'
// 1. Initialize and run local WebSocket server
mut ws_server := websocket.new_server(.ip, port, '/')
ws_server.on_connect(fn (mut s websocket.ServerClient) !bool {
println('Server: Client connected from ${s.client_key}')
return true
})!
// Server message handler: validates payload size, decodes JSON, and routes actions.
ws_server.on_message(fn (mut ws websocket.Client, msg &websocket.Message) ! {
if msg.opcode == .text_frame {
payload := msg.payload.bytestr()
// Safety check: Enforce maximum payload size limit (e.g., 2048 bytes) to prevent DoS (OOM)
max_allowed_len := 2048
if payload.len > max_allowed_len {
println('Server: Rejected message of size ${payload.len} (exceeds ${max_allowed_len} limit)')
// Close connection with code 1009 (Message Too Big)
ws.close(1009, 'Message size exceeds limit') or {}
return
}
// Decode the JSON protocol message
ws_msg := json.decode(WsMessage, payload) or {
println('Server: Invalid JSON protocol: ${err}')
err_resp := json.encode(WsMessage{ action: 'error', data: 'invalid json' })
ws.write_string(err_resp) or {}
return
}
println('Server received action "${ws_msg.action}" with data (len: ${ws_msg.data.len})')
match ws_msg.action {
'ping' {
resp := json.encode(WsMessage{ action: 'pong', data: ws_msg.data })
ws.write_string(resp)!
}
'goodbye' {
println('Server received goodbye action. Replying and closing...')
resp := json.encode(WsMessage{ action: 'goodbye_ack', data: 'Goodbye!' })
ws.write_string(resp)!
// Clean close from server side
ws.close(1000, 'done') or {}
}
else {
println('Server: Unknown action: ${ws_msg.action}')
}
}
}
})
// Start the server listen loop in a background thread
spawn fn [mut ws_server] () {
ws_server.listen() or { println('Server error: ${err}') }
}()
// Allow the server a moment to start
time.sleep(100 * time.millisecond)
// 2. RUN CLIENT CONNECTION 1: Clean ping-pong and goodbye handshake
println('\n--- Connection 1: Standard Chat / Ping-Pong ---')
mut ws_client1 := websocket.new_client(uri) or {
println('Client 1 init failed: ${err}')
return
}
mut state1 := &ClientState{
count: 0
}
ws_client1.on_open(fn (mut c websocket.Client) ! {
println('Client 1: Connection opened!')
// Initiate the first Ping message
ping_msg := json.encode(WsMessage{ action: 'ping', data: '1' })
c.write_string(ping_msg)!
})
ws_client1.on_message(fn [mut state1] (mut c websocket.Client, msg &websocket.Message) ! {
if msg.opcode == .text_frame {
payload := msg.payload.bytestr()
ws_msg := json.decode(WsMessage, payload) or { return }
println('Client 1 received response action "${ws_msg.action}" with data: "${ws_msg.data}"')
if ws_msg.action == 'pong' {
state1.count++
if state1.count < 3 {
next_ping := json.encode(WsMessage{ action: 'ping', data: '${state1.count + 1}' })
println('Client 1 sending: "${next_ping}"')
c.write_string(next_ping)!
} else {
goodbye := json.encode(WsMessage{ action: 'goodbye', data: 'Goodbye' })
println('Client 1 sending goodbye: "${goodbye}"')
c.write_string(goodbye)!
}
} else if ws_msg.action == 'goodbye_ack' {
println('Client 1 received goodbye ack. Client closing connection.')
c.close(1000, 'Done') or {}
}
}
})
ws_client1.on_close(fn (mut c websocket.Client, code int, reason string) ! {
println('Client 1: Connection closed (code: ${code}, reason: "${reason}")')
})
ws_client1.on_error(fn (mut c websocket.Client, error_msg string) ! {
println('Client 1 error: ${error_msg}')
})
ws_client1.connect() or {
println('Client 1 failed to connect: ${err}')
return
}
spawn ws_client1.listen()
// Wait for the first flow to complete
time.sleep(600 * time.millisecond)
// 3. RUN CLIENT CONNECTION 2: Reject oversized message
println('\n--- Connection 2: Security Validation (Oversized Message) ---')
mut ws_client2 := websocket.new_client(uri) or {
println('Client 2 init failed: ${err}')
return
}
ws_client2.on_open(fn (mut c websocket.Client) ! {
println('Client 2: Connection opened!')
// Send oversized data (3000 bytes, exceeding server 2048-byte limit)
large_payload := 'A'.repeat(3000)
large_msg := json.encode(WsMessage{ action: 'ping', data: large_payload })
println('Client 2 sending oversized payload (size: ${large_msg.len} bytes)...')
c.write_string(large_msg)!
})
ws_client2.on_close(fn (mut c websocket.Client, code int, reason string) ! {
println('Client 2: Connection closed (code: ${code}, reason: "${reason}")')
if code == 1009 {
println('Client 2: Successfully verified server rejected oversized message with code 1009!')
} else if code == 1000 {
// Ignore standard teardown close
} else {
println('Client 2: Unexpected close code: ${code}')
}
})
ws_client2.on_error(fn (mut c websocket.Client, error_msg string) ! {
println('Client 2 error: ${error_msg}')
})
ws_client2.connect() or {
println('Client 2 failed to connect: ${err}')
return
}
spawn ws_client2.listen()
// Wait for the second flow to finish
time.sleep(500 * time.millisecond)
// Clean close of server listener
println('\nWebSocket Protocol Demo finished.')
}
Net Html
Net Html
The standard library net.html module provides light-weight parsing and querying APIs for HTML documents. It parses an HTML raw string into a structured hierarchical Document Object Model (DOM) tree of nodes. Developers can query this parsed DOM tree using methods to find tags by element name, filter by specific CSS class names, or read attributes (e.g. href inside <a> tags). This is extremely useful for building web scrapers, crawler services, or content extractors without needing external dependencies.
This example illustrates parsing an HTML string, navigating node structures, filtering tags by attributes, and printing text values.
Additional Context from Repository docs:
This example demonstrates parsing HTML strings, querying tags by class name and attribute values, and extracting node text and properties using the net.html module.
module main
import net.html
fn main() {
println('=== net.html Module Demo ===')
// 1. Define a sample HTML document to parse
html_content := '
<!DOCTYPE html>
<html>
<head>
<title>V Programming Language</title>
</head>
<body>
<header>
<h1 class="main-title">Welcome to the V Standard Library</h1>
</header>
<main>
<div class="content" id="overview">
<p class="description">
V is a simple, fast, safe, and compiled language.
</p>
<p class="description">
The net.html module parses HTML into a Queryable Document Object Model (DOM).
</p>
<a href="https://vlang.io" class="link-btn" id="home-link">Official Website</a>
<a href="https://github.com/vlang/v" class="link-btn" id="repo-link">GitHub Repository</a>
</div>
</main>
</body>
</html>'
// 2. Parse the HTML string into a DocumentObjectModel (DOM)
println('Parsing HTML document...')
dom := html.parse(html_content)
// 3. Retrieve tags by name (e.g., header, title)
title_tags := dom.get_tags(name: 'title')
if title_tags.len > 0 {
println('Page Title: "${title_tags[0].text()}"')
}
// 4. Retrieve tags by class name
descriptions := dom.get_tags_by_class_name('description')
println('\nParagraphs with class "description":')
for i, p in descriptions {
println(' ${i + 1}: ${p.text().trim_space()}')
}
// 5. Query tags by attribute value (e.g., href, id)
links := dom.get_tags_by_class_name('link-btn')
println('\nLinks found in document:')
for link in links {
href := link.attributes['href']
id := link.attributes['id']
text := link.text()
println(' - Text: "${text}"')
println(' ID: "${id}"')
println(' URL: "${href}"')
}
// 6. Access DOM root and verify serialization representation
root := dom.get_root()
println('\nDOM Root Element Tag Name: <${root.name}>')
println('HTML Demo finished.')
}
Net Jsonrpc
This example demonstrates implementing JSON-RPC 2.0 servers and clients using V's net.jsonrpc module, utilizing a Unix domain socket connection as the transport layer.
module main
import net.unix
import net.jsonrpc
import os
import time
// Define structs for request parameters and response results
struct MathParams {
pub:
a int
b int
}
struct MathResult {
pub:
sum int
difference int
}
// Router handler to compute mathematical operations
fn handle_math(req &jsonrpc.Request, mut wr jsonrpc.ResponseWriter) {
// Decode request parameters into MathParams struct
params := req.decode_params[MathParams]() or {
wr.write_error(jsonrpc.invalid_params)
return
}
result := MathResult{
sum: params.a + params.b
difference: params.a - params.b
}
// Write the successful result back
wr.write(result)
}
// Start JSON-RPC 2.0 Server over Unix Socket
fn run_rpc_server(socket_path string) ! {
// Ensure cleanup of any old socket file
if os.exists(socket_path) {
os.rm(socket_path)!
}
mut listener := unix.listen_stream(socket_path, unix.ListenOptions{}) or {
println('Server: Failed to listen: ${err}')
return err
}
defer {
listener.close() or {}
listener.unlink() or {}
}
println('Server: Listening and waiting for connections...')
// Accept client connection
mut conn := listener.accept() or {
println('Server: Accept failed: ${err}')
return err
}
defer {
conn.close() or {}
}
println('Server: Client connected, initiating JSON-RPC protocol.')
// Setup JSON-RPC router and register math method
mut router := jsonrpc.Router{}
router.register('math.compute', handle_math)
// Create JSON-RPC server wrapping the Unix socket connection stream
mut server := jsonrpc.new_server(jsonrpc.ServerConfig{
stream: conn
handler: router.handle_jsonrpc
})
// Process incoming request and respond
server.respond() or {
println('Server: Error processing request: ${err}')
return err
}
println('Server: Successfully processed request and shut down.')
}
// Start JSON-RPC 2.0 Client over Unix Socket
fn run_rpc_client(socket_path string) ! {
println('Client: Connecting to server at ${socket_path}...')
mut conn := unix.connect_stream(socket_path) or {
println('Client: Connection failed: ${err}')
return err
}
defer {
conn.close() or {}
}
// Create JSON-RPC client wrapping the Unix socket connection stream
mut client := jsonrpc.new_client(jsonrpc.ClientConfig{
stream: conn
})
params := MathParams{
a: 45
b: 17
}
println('Client: Sending request "math.compute" with params {a: ${params.a}, b: ${params.b}}')
// Execute JSON-RPC request (method, parameters, request ID)
resp := client.request('math.compute', params, 'req_math_1') or {
println('Client: Request execution failed: ${err}')
return err
}
// Decode response result
result := resp.decode_result[MathResult]() or {
println('Client: Failed to decode response result: ${err}')
return err
}
println('Client received response:')
println(' Request ID: ${resp.id}')
println(' Result sum: ${result.sum}')
println(' Result difference: ${result.difference}')
}
fn main() {
println('=== net.jsonrpc Module Demo ===')
socket_path := os.join_path(os.temp_dir(), 'v_jsonrpc_example_socket')
// Spawn JSON-RPC server in background thread
spawn fn (path string) {
run_rpc_server(path) or { println('Server thread failed: ${err}') }
}(socket_path)
// Wait briefly for the server socket to bind
time.sleep(100 * time.millisecond)
// Run JSON-RPC client in main thread
run_rpc_client(socket_path) or { println('Client thread failed: ${err}') }
// Wait briefly for server post-handling cleanups
time.sleep(50 * time.millisecond)
println('JSON-RPC Demo finished.')
}
Net Jsonrpc Persistent
This example demonstrates how to build a persistent JSON-RPC 2.0 connection. The server calls server.start() to continuously process incoming requests, and the client sends multiple method invocations sequentially over a single socket connection.
module main
import net.unix
import net.jsonrpc
import os
import time
// Define structs for request parameters and response results
struct MathParams {
pub:
a int
b int
}
struct MathResult {
pub:
sum int
difference int
}
// Router handler to compute mathematical operations
fn handle_math(req &jsonrpc.Request, mut wr jsonrpc.ResponseWriter) {
params := req.decode_params[MathParams]() or {
wr.write_error(jsonrpc.invalid_params)
return
}
result := MathResult{
sum: params.a + params.b
difference: params.a - params.b
}
wr.write(result)
}
// Start JSON-RPC 2.0 Server over Unix Socket in a persistent loop
fn run_rpc_server(socket_path string) ! {
if os.exists(socket_path) {
os.rm(socket_path)!
}
mut listener := unix.listen_stream(socket_path, unix.ListenOptions{}) or {
println('Server: Failed to listen: ${err}')
return err
}
defer {
listener.close() or {}
listener.unlink() or {}
}
println('Server: Listening and waiting for connections...')
mut conn := listener.accept() or {
println('Server: Accept failed: ${err}')
return err
}
defer {
conn.close() or {}
}
println('Server: Client connected, starting persistent JSON-RPC loop.')
mut router := jsonrpc.Router{}
router.register('math.compute', handle_math)
mut server := jsonrpc.new_server(jsonrpc.ServerConfig{
stream: conn
handler: router.handle_jsonrpc
})
// Start the server processing loop (calls s.respond() in a loop)
server.start()
println('Server: Loop finished.')
}
// Start JSON-RPC 2.0 Client and make multiple requests over the same connection
fn run_rpc_client(socket_path string) ! {
println('Client: Connecting to server at ${socket_path}...')
mut conn := unix.connect_stream(socket_path) or {
println('Client: Connection failed: ${err}')
return err
}
defer {
conn.close() or {}
}
mut client := jsonrpc.new_client(jsonrpc.ClientConfig{
stream: conn
})
// Perform multiple requests over the same persistent connection
for i in 1 .. 4 {
params := MathParams{
a: 10 * i
b: 5 * i
}
req_id := 'req_math_${i}'
println('Client: Sending request "${req_id}" for math.compute with {a: ${params.a}, b: ${params.b}}')
resp := client.request('math.compute', params, req_id) or {
println('Client: Request failed: ${err}')
return err
}
result := resp.decode_result[MathResult]() or {
println('Client: Failed to decode result: ${err}')
return err
}
println('Client received response for ${resp.id}:')
println(' sum: ${result.sum}')
println(' difference: ${result.difference}')
time.sleep(50 * time.millisecond)
}
}
fn main() {
println('=== Persistent net.jsonrpc Module Demo ===')
socket_path := os.join_path(os.temp_dir(), 'v_jsonrpc_persistent_socket')
// Spawn JSON-RPC server in background thread
spawn fn (path string) {
run_rpc_server(path) or { println('Server thread failed: ${err}') }
}(socket_path)
// Wait briefly for the server socket to bind
time.sleep(100 * time.millisecond)
// Run JSON-RPC client in main thread
run_rpc_client(socket_path) or { println('Client thread failed: ${err}') }
// Wait briefly for server post-handling cleanups
time.sleep(50 * time.millisecond)
println('Persistent JSON-RPC Demo finished.')
}
Net Ssl
This example demonstrates setting up a secure SSL/TLS server and client connection using the standard library's net.mbedtls module, including programmatically generating a self-signed key/cert pair using OpenSSL and cleaning them up on exit.
module main
import net.mbedtls
import net
import os
import time
// generate_certs runs openssl to create a temporary self-signed certificate and key.
fn generate_certs() ! {
println('Generating temporary self-signed SSL certificate...')
res := os.execute('openssl req -x509 -newkey rsa:2048 -keyout temp_server.key -out temp_server.crt -days 1 -nodes -subj "/CN=localhost"')
if res.exit_code != 0 {
return error('Failed to generate certs: ${res.output}')
}
}
// cleanup_certs deletes the temporary certificate and key files.
fn cleanup_certs() {
println('Cleaning up temporary certificate files...')
os.rm('temp_server.key') or {}
os.rm('temp_server.crt') or {}
}
// run_server starts the SSL server, accepts a client connection,
// reads a message, responds securely, and exits.
fn run_server(port int) ! {
config := mbedtls.SSLConnectConfig{
cert: 'temp_server.crt'
cert_key: 'temp_server.key'
validate: false
}
mut listener := mbedtls.new_ssl_listener('127.0.0.1:${port}', config) or {
println('Server: Failed to create listener: ${err}')
return err
}
defer {
listener.shutdown() or {}
}
println('Server: Listening on SSL port ${port}...')
mut conn := listener.accept() or {
println('Server: Failed to accept SSL connection: ${err}')
return err
}
defer {
conn.close() or {}
}
println('Server: SSL Client connected!')
mut buf := []u8{len: 1024}
n := conn.read(mut buf) or {
println('Server: Read failed: ${err}')
return err
}
message := buf[..n].bytestr()
println('Server: Received message: "${message}"')
// Respond securely
response := 'Echo Secure: ${message}'
conn.write(response.bytes()) or {
println('Server: Write failed: ${err}')
return err
}
println('Server: Sent secure response.')
}
// run_client connects to the server port via TCP first, wraps it in SSL,
// sends a message, reads the secure response, and closes.
fn run_client(port int) ! {
println('Client: Dialing standard TCP port first...')
mut tcp_conn := net.dial_tcp('127.0.0.1:${port}') or {
println('Client: Failed to connect standard TCP: ${err}')
return err
}
println('Client: Initiating SSL handshake on top of TCP connection...')
config := mbedtls.SSLConnectConfig{
validate: false
}
mut ssl_conn := mbedtls.new_ssl_conn(config) or {
println('Client: Failed to create SSL connection struct: ${err}')
return err
}
defer {
ssl_conn.close() or {}
}
ssl_conn.connect(mut tcp_conn, 'localhost') or {
println('Client: SSL handshake failed: ${err}')
return err
}
println('Client: Secure connection established!')
message := 'Hello V Secure Sockets!'
println('Client: Sending message: "${message}"')
ssl_conn.write(message.bytes()) or {
println('Client: Write failed: ${err}')
return err
}
mut buf := []u8{len: 1024}
n := ssl_conn.read(mut buf) or {
println('Client: Read failed: ${err}')
return err
}
response := buf[..n].bytestr()
println('Client: Received response: "${response}"')
}
fn main() {
println('=== net.ssl Module Demo ===')
generate_certs() or {
println('Error generating certs: ${err}')
return
}
defer {
cleanup_certs()
}
port := 38295
// Spawn server in background
spawn fn (p int) {
run_server(p) or { println('Server thread failed: ${err}') }
}(port)
// Wait briefly for server to bind
time.sleep(200 * time.millisecond)
// Run client in main thread
run_client(port) or { println('Client failed: ${err}') }
time.sleep(50 * time.millisecond)
println('SSL Demo finished.')
}
Ssl Persistent
This example demonstrates keeping an SSL/TLS connection open for multiple rounds of secure back-and-forth ping-pong communication over net.mbedtls.
module main
import net.mbedtls
import net
import os
import time
// generate_certs runs openssl to create a temporary self-signed certificate and key.
fn generate_certs() ! {
println('Generating temporary self-signed SSL certificate...')
res := os.execute('openssl req -x509 -newkey rsa:2048 -keyout temp_server.key -out temp_server.crt -days 1 -nodes -subj "/CN=localhost"')
if res.exit_code != 0 {
return error('Failed to generate certs: ${res.output}')
}
}
// cleanup_certs deletes the temporary certificate and key files.
fn cleanup_certs() {
println('Cleaning up temporary certificate files...')
os.rm('temp_server.key') or {}
os.rm('temp_server.crt') or {}
}
// run_server starts the SSL server, accepts a client connection,
// and processes incoming messages in a loop until the client sends "Goodbye".
fn run_server(port int) ! {
config := mbedtls.SSLConnectConfig{
cert: 'temp_server.crt'
cert_key: 'temp_server.key'
validate: false
}
mut listener := mbedtls.new_ssl_listener('127.0.0.1:${port}', config) or {
println('Server: Failed to create listener: ${err}')
return err
}
defer {
listener.shutdown() or {}
}
println('Server: Listening on SSL port ${port}...')
mut conn := listener.accept() or {
println('Server: Failed to accept SSL connection: ${err}')
return err
}
defer {
conn.close() or {}
}
println('Server: SSL Client connected!')
// Loop to handle back-and-forth messages on the same secure connection
for {
mut buf := []u8{len: 1024}
n := conn.read(mut buf) or {
println('Server: Secure connection closed or read error: ${err}')
break
}
if n == 0 {
println('Server: Client disconnected.')
break
}
message := buf[..n].bytestr()
println('Server received secure: "${message}"')
if message == 'Goodbye' {
println('Server received Goodbye. Replying and closing secure connection...')
conn.write('Goodbye!'.bytes()) or { println('Server: Write failed: ${err}') }
break
}
response := 'Echo: ${message}'
println('Server sending secure: "${response}"')
conn.write(response.bytes()) or {
println('Server: Write failed: ${err}')
break
}
}
println('Server finished.')
}
// run_client connects to the server port via TCP first, wraps it in SSL,
// and sends multiple messages in a loop before ending the secure session cleanly.
fn run_client(port int) ! {
println('Client: Dialing standard TCP port first...')
mut tcp_conn := net.dial_tcp('127.0.0.1:${port}') or {
println('Client: Failed to connect standard TCP: ${err}')
return err
}
println('Client: Initiating SSL handshake on top of TCP connection...')
config := mbedtls.SSLConnectConfig{
validate: false
}
mut ssl_conn := mbedtls.new_ssl_conn(config) or {
println('Client: Failed to create SSL connection struct: ${err}')
return err
}
defer {
ssl_conn.close() or {}
}
ssl_conn.connect(mut tcp_conn, 'localhost') or {
println('Client: SSL handshake failed: ${err}')
return err
}
println('Client: Secure connection established!')
// Exchange multiple messages
for i in 1 .. 4 {
message := 'Ping ${i}'
println('Client sending secure: "${message}"')
ssl_conn.write(message.bytes()) or {
println('Client: Write failed: ${err}')
return err
}
// Read response
mut buf := []u8{len: 1024}
n := ssl_conn.read(mut buf) or {
println('Client: Read failed: ${err}')
return err
}
if n == 0 {
println('Client: Server closed secure connection.')
return error('Server closed connection unexpectedly')
}
response := buf[..n].bytestr()
println('Client received secure response: "${response}"')
time.sleep(50 * time.millisecond)
}
// Send Goodbye to cleanly terminate the persistent session
println('Client sending: "Goodbye"')
ssl_conn.write('Goodbye'.bytes()) or {
println('Client: Write failed: ${err}')
return err
}
mut buf := []u8{len: 1024}
n := ssl_conn.read(mut buf) or {
println('Client: Read failed: ${err}')
return err
}
if n > 0 {
response := buf[..n].bytestr()
println('Client received secure response: "${response}"')
}
println('Client finished.')
}
fn main() {
println('=== Persistent SSL Demo ===')
generate_certs() or {
println('Error generating certs: ${err}')
return
}
defer {
cleanup_certs()
}
port := 38296
// Spawn the server in a background thread
spawn fn (p int) {
run_server(p) or { println('Server thread failed: ${err}') }
}(port)
// Allow the server thread a short time to start and bind
time.sleep(200 * time.millisecond)
// Run the client in the main thread
run_client(port) or { println('Client failed: ${err}') }
// Give the server a small window to finish deferred cleanups
time.sleep(50 * time.millisecond)
println('SSL Sockets Demo finished.')
}
Net Tcp
Net Tcp
The net standard library module provides full support for TCP (Transmission Control Protocol) stream networking. TCP is a connection-oriented, reliable transport protocol that guarantees ordered, error-checked delivery of streams of octets between hosts. In V, you construct a TCP server using net.listen_tcp which binds to an IP address and port, returning a listener instance. Calling accept() on the listener blocks execution until an incoming client initiates a connection. Communication is performed via stream reading and writing operations directly on the connection sockets.
This example illustrates creating a TCP server and client, opening socket connections, sending payload data, and handling connection cleanup blocks.
Additional Context from Repository docs:
This example demonstrates how to create a simple TCP server and client in V. The server listens on a local port, accepts an incoming client connection, receives data, sends a response, and closes the connection.
module main
import net
import time
// run_server starts the TCP server on the specified port, accepts a connection,
// reads a message, responds, and closes the connection.
fn run_server(port int) ! {
mut listener := net.listen_tcp(.ip, '127.0.0.1:${port}') or {
println('Server: Failed to listen on port ${port}: ${err}')
return err
}
defer {
listener.close() or {}
}
println('Server: Listening on 127.0.0.1:${port}...')
mut conn := listener.accept() or {
println('Server: Failed to accept connection: ${err}')
return err
}
defer {
conn.close() or {}
}
println('Server: Client connected!')
mut buf := []u8{len: 1024}
n := conn.read(mut buf) or {
println('Server: Read failed: ${err}')
return err
}
message := buf[..n].bytestr()
println('Server: Received message: "${message}"')
// Write response back to the client
response := 'Echo: ${message}'
conn.write(response.bytes()) or {
println('Server: Write failed: ${err}')
return err
}
println('Server: Sent echo response.')
}
// run_client connects to the TCP server, sends a message, reads the response,
// and closes the connection.
fn run_client(port int) ! {
println('Client: Connecting to 127.0.0.1:${port}...')
mut conn := net.dial_tcp('127.0.0.1:${port}') or {
println('Client: Failed to connect: ${err}')
return err
}
defer {
conn.close() or {}
}
println('Client: Connected!')
// Send message to the server
message := 'Hello V TCP Sockets!'
println('Client: Sending message: "${message}"')
conn.write(message.bytes()) or {
println('Client: Write failed: ${err}')
return err
}
// Read server response
mut buf := []u8{len: 1024}
n := conn.read(mut buf) or {
println('Client: Read failed: ${err}')
return err
}
response := buf[..n].bytestr()
println('Client: Received response: "${response}"')
}
fn main() {
println('=== net.tcp Module Demo ===')
port := 38290
// Spawn the server in a background thread
spawn fn (p int) {
run_server(p) or { println('Server thread failed: ${err}') }
}(port)
// Allow the server thread a short time to start and bind
time.sleep(100 * time.millisecond)
// Run the client in the main thread
run_client(port) or { println('Client failed: ${err}') }
// Give the server a small window to finish deferred cleanups
time.sleep(50 * time.millisecond)
println('TCP Demo finished.')
}
Tcp Persistent
This example demonstrates a persistent length-prefixed TCP connection, processing payloads in chunks, and rejecting messages exceeding safety size boundaries.
module main
import net
import time
const max_message_size = 8192
// write_msg sends a message using a 4-byte magic signature and a 4-byte big-endian length in a single write syscall.
fn write_msg(mut conn net.TcpConn, payload string) ! {
mut buf := []u8{len: 8 + payload.len}
buf[0] = `M`
buf[1] = `S`
buf[2] = `G`
buf[3] = `0`
buf[4] = u8((u32(payload.len) >> 24) & 0xff)
buf[5] = u8((u32(payload.len) >> 16) & 0xff)
buf[6] = u8((u32(payload.len) >> 8) & 0xff)
buf[7] = u8(u32(payload.len) & 0xff)
if payload.len > 0 {
unsafe {
C.memcpy(&buf[8], payload.str, payload.len)
}
}
// Send consolidated buffer in a single system call
conn.write(buf) or { return err }
}
// read_exact reads exactly `size` bytes from the connection, processing data in chunks.
// Real-world performance optimization: Reads directly into mutable slice views of our pre-allocated
// buffer to achieve zero-allocation reads inside the chunking loop.
fn read_exact(mut conn net.TcpConn, size int) ![]u8 {
mut data := []u8{len: size}
mut read_bytes := 0
for read_bytes < size {
remaining := size - read_bytes
// Use a small buffer chunk limit (e.g. 512 bytes) to demonstrate reading in chunks
chunk_limit := if remaining > 512 { 512 } else { remaining }
n := conn.read(mut data[read_bytes..read_bytes + chunk_limit]) or { return err }
if n == 0 {
if read_bytes == 0 {
return error('EOF')
}
return error('unexpected end of stream')
}
read_bytes += n
}
return data
}
// read_msg reads a single framed message.
fn read_msg(mut conn net.TcpConn, max_size int) !string {
// Read header: 4 magic bytes + 4 length bytes = 8 bytes
header_bytes := read_exact(mut conn, 8) or { return err }
// Validate protocol magic bytes
if header_bytes[0] != `M` || header_bytes[1] != `S` || header_bytes[2] != `G`
|| header_bytes[3] != `0` {
return error('invalid protocol magic bytes')
}
// Reconstruct big-endian length
len := int((u32(header_bytes[4]) << 24) | (u32(header_bytes[5]) << 16) | (u32(header_bytes[6]) << 8) | u32(header_bytes[7]))
// Real-world security boundary: Reject messages larger than allowed limit to prevent DoS (OOM)
if len > max_size {
return error('message size ${len} exceeds limit of ${max_size} bytes')
}
if len < 0 {
return error('invalid negative message length')
}
// Read the actual payload
payload_bytes := read_exact(mut conn, len) or { return err }
return payload_bytes.bytestr()
}
// run_server starts the TCP server on the specified port, accepts a connection,
// and processes incoming messages in a loop according to our framing protocol.
fn run_server(port int) ! {
mut listener := net.listen_tcp(.ip, '127.0.0.1:${port}') or {
println('Server: Failed to listen on port ${port}: ${err}')
return err
}
defer {
listener.close() or {}
}
println('Server: Listening on 127.0.0.1:${port}...')
mut conn := listener.accept() or {
println('Server: Failed to accept connection: ${err}')
return err
}
defer {
conn.close() or {}
}
println('Server: Client connected!')
// Real-world safety practice: Set read and write timeouts to prevent connection hang-ups (Slowloris DoS)
conn.set_read_timeout(time.second * 5)
conn.set_write_timeout(time.second * 5)
for {
message := read_msg(mut conn, max_message_size) or {
if err.msg() == 'EOF' {
println('Server: Client disconnected cleanly (EOF).')
} else {
println('Server: Connection closed or protocol error: ${err}')
}
break
}
// Preview message content
preview_len := if message.len > 30 { 30 } else { message.len }
println('Server received message (len: ${message.len}): "${message[..preview_len]}"...')
if message == 'Goodbye' {
println('Server received Goodbye. Replying and closing connection...')
write_msg(mut conn, 'Goodbye!') or { println('Server: Write failed: ${err}') }
break
}
response := 'Echo: ${message}'
write_msg(mut conn, response) or {
println('Server: Write failed: ${err}')
break
}
}
println('Server finished.')
}
// run_client connects to the TCP server, sends multiple messages (including
// a large chunked message and an invalid/overflow message), and validates responses.
fn run_client(port int) ! {
println('Client: Connecting to 127.0.0.1:${port}...')
mut conn := net.dial_tcp('127.0.0.1:${port}') or {
println('Client: Failed to connect: ${err}')
return err
}
defer {
conn.close() or {}
}
println('Client: Connected!')
// Set connection timeouts for the client too
conn.set_read_timeout(time.second * 5)
conn.set_write_timeout(time.second * 5)
// 1. Send a standard small message
msg1 := 'Ping 1'
println('Client sending small message: "${msg1}"')
write_msg(mut conn, msg1)!
resp1 := read_msg(mut conn, max_message_size)!
println('Client received response: "${resp1}"')
time.sleep(50 * time.millisecond)
// 2. Send a large message within limit (5000 bytes) to trigger chunked read assembly
msg2 := 'A'.repeat(5000)
println('Client sending large message of length ${msg2.len}...')
write_msg(mut conn, msg2)!
resp2 := read_msg(mut conn, max_message_size)!
println('Client received response of length ${resp2.len} successfully!')
time.sleep(50 * time.millisecond)
// 3. Attempt to send an invalid/overflow message (header length > max_message_size)
println('Client sending invalid header claiming 100,000 bytes payload...')
magic := [u8(`M`), `S`, `G`, `0`]
bad_len_bytes := [u8(0), 1, 134, 160] // 100,000 big-endian
conn.write(magic)!
conn.write(bad_len_bytes)!
// The server must reject the message and terminate the connection
mut buf := []u8{len: 1}
n := conn.read(mut buf) or {
println('Client: Successfully verified server rejected overflow and closed connection: ${err}')
return
}
if n == 0 {
println('Client: Successfully verified server rejected overflow (EOF received).')
} else {
println('Client: Warning - Server did not close connection on overflow!')
}
}
fn main() {
println('=== Persistent TCP Protocol Demo ===')
port := 38293
// Spawn the server in a background thread
spawn fn (p int) {
run_server(p) or { println('Server thread failed: ${err}') }
}(port)
// Allow the server thread a short time to start and bind
time.sleep(100 * time.millisecond)
// Run the client in the main thread
run_client(port) or { println('Client failed: ${err}') }
// Give the server a small window to finish deferred cleanups
time.sleep(50 * time.millisecond)
println('TCP Protocol Demo finished.')
}
Deep Dive Explanation: Persistent TCP & Custom Protocol Framing
1. Why Message Framing is Essential
TCP is a byte-stream protocol. It does not have any concept of packet or message boundaries; it guarantees only that bytes arrive in order. If a client writes two messages of 100 bytes, the server might read them as a single chunk of 200 bytes, or as several chunks of arbitrary sizes (e.g., 50 and 150 bytes).
To transmit individual messages safely over a persistent connection, we define a custom Framing Protocol:
- Magic Bytes (4 bytes): The message starts with a signature (
MSG0in ASCII). This acts as a sanity check. If the server receives something else, it knows the stream is corrupted or the client is using an incorrect protocol. - Length Prefix (4 bytes): A big-endian 32-bit integer indicating the exact size of the following payload.
- Payload: The actual raw message bytes.
2. Zero-Allocation Chunked Reading (`read_exact`)
The read_exact(mut conn, size) function reads bytes in a loop until the full requested size is reached:
n := conn.read(mut data[read_bytes .. read_bytes + chunk_limit])
- Performance Optimization: Instead of allocating new buffers in each iteration of the loop, V uses slice expressions (
data[start..end]) to pass a mutable reference to a specific sub-range of the pre-allocateddataarray directly to the socket read function. This avoids any dynamic memory allocation, optimizing throughput and memory usage.
3. Security Boundaries & DoS Protection
Production socket servers must defend against malicious input and network timeouts:
- Max Payload Checking: In
read_msg, the server reads the length prefix from the header. Iflen > max_size(e.g., larger than8192bytes), the server immediately rejects the message and closes the connection. Without this check, a client could claim a message size of 2 GB, forcing the server to allocate a huge array and crash due to Out-Of-Memory (OOM). - Connection Timeouts: Calling
conn.set_read_timeoutandconn.set_write_timeoutprevents threads from blocking indefinitely. If a client connects and then stops sending data (a Slowloris attack), the server will automatically close the socket after the timeout expires (5 seconds in this demo).
Net Udp
Net Udp
UDP (User Datagram Protocol) is a connectionless, unreliable transport protocol that sends independent packets (datagrams) without establishing a dedicated channel. Unlike TCP, UDP has no handshakes, retry logic, or packet ordering guarantees, which makes it highly lightweight and perfect for low-latency tasks like real-time gaming, video streams, or DNS queries. In V, you open a UDP socket using net.listen_udp. Incoming datagrams are read along with their sender IP/port address (using read or recvfrom), and replies are routed back using the connectionless write_to method.
This example illustrates opening a UDP socket listener, sending datagram packets, extracting sender details, and replying to a dynamic port.
Additional Context from Repository docs:
This example demonstrates sending and receiving connectionless UDP packets. The server binds to a local port and receives a message along with the sender's address, and responds to it using write_to.
module main
import net
import time
// run_server starts the UDP server on the specified port, listens for a packet,
// prints the message, sends a response back to the sender, and exits.
fn run_server(port int) ! {
mut socket := net.listen_udp('127.0.0.1:${port}') or {
println('Server: Failed to listen on port ${port}: ${err}')
return err
}
defer {
socket.close() or {}
}
println('Server: Listening for UDP packets on port ${port}...')
mut buf := []u8{len: 1024}
read, addr := socket.read(mut buf) or {
println('Server: Read failed: ${err}')
return err
}
message := buf[..read].bytestr()
println('Server: Received message from ${addr}: "${message}"')
// Send echo response
response := 'Echo: ${message}'
socket.write_to(addr, response.bytes()) or {
println('Server: Send failed: ${err}')
return err
}
println('Server: Sent echo response.')
}
// run_client creates a UDP client socket, sends a datagram, and waits for a response.
fn run_client(port int) ! {
mut socket := net.dial_udp('127.0.0.1:${port}') or {
println('Client: Failed to dial server: ${err}')
return err
}
defer {
socket.close() or {}
}
message := 'Hello V UDP Sockets!'
println('Client: Sending message: "${message}"')
socket.write(message.bytes()) or {
println('Client: Write failed: ${err}')
return err
}
// Read server response
mut buf := []u8{len: 1024}
read, addr := socket.read(mut buf) or {
println('Client: Read failed: ${err}')
return err
}
response := buf[..read].bytestr()
println('Client: Received response from ${addr}: "${response}"')
}
fn main() {
println('=== net.udp Module Demo ===')
port := 38291
// Spawn the server in a background thread
spawn fn (p int) {
run_server(p) or { println('Server thread failed: ${err}') }
}(port)
// Allow the server thread a short time to start and bind
time.sleep(100 * time.millisecond)
// Run the client in the main thread
run_client(port) or { println('Client failed: ${err}') }
// Give the server a small window to finish deferred cleanups
time.sleep(50 * time.millisecond)
println('UDP Demo finished.')
}
Udp Persistent
This example demonstrates application-level packet fragmentation, reassembly, and fragment count limit enforcement over UDP.
module main
import net
import time
// UdpReassembler stores state for reassembling fragmented UDP packets.
struct UdpReassembler {
mut:
fragments map[int][]u8
total int
last_seen time.Time
}
// write_udp_msg fragments and sends a message to dialed destination.
// Real-world performance optimization: Avoids allocating a full payload copy by writing fragments
// directly from string memory slices using C.memcpy.
fn write_udp_msg(mut socket net.UdpConn, payload string) ! {
chunk_size := 1024
total_frags := (payload.len + chunk_size - 1) / chunk_size
if total_frags == 0 {
header := [u8(`U`), `D`, `P`, `0`, 0, 1, 0, 0]
socket.write(header) or { return err }
return
}
for i in 0 .. total_frags {
start := i * chunk_size
mut end := (i + 1) * chunk_size
if end > payload.len {
end = payload.len
}
frag_len := end - start
mut packet := []u8{len: 8 + frag_len}
packet[0] = `U`
packet[1] = `D`
packet[2] = `P`
packet[3] = `0`
packet[4] = u8(i)
packet[5] = u8(total_frags)
packet[6] = u8((u32(frag_len) >> 8) & 0xff)
packet[7] = u8(u32(frag_len) & 0xff)
if frag_len > 0 {
unsafe {
C.memcpy(&packet[8], payload.str + start, frag_len)
}
}
socket.write(packet) or { return err }
// Sleep briefly to avoid packet loss during loopback transmission
time.sleep(2 * time.millisecond)
}
}
// write_udp_msg_to fragments and sends a message to a specific address using write_to.
// Real-world performance optimization: Avoids allocating a full payload copy by writing fragments
// directly from string memory slices using C.memcpy.
fn write_udp_msg_to(mut socket net.UdpConn, addr net.Addr, payload string) ! {
chunk_size := 1024
total_frags := (payload.len + chunk_size - 1) / chunk_size
if total_frags == 0 {
header := [u8(`U`), `D`, `P`, `0`, 0, 1, 0, 0]
socket.write_to(addr, header) or { return err }
return
}
for i in 0 .. total_frags {
start := i * chunk_size
mut end := (i + 1) * chunk_size
if end > payload.len {
end = payload.len
}
frag_len := end - start
mut packet := []u8{len: 8 + frag_len}
packet[0] = `U`
packet[1] = `D`
packet[2] = `P`
packet[3] = `0`
packet[4] = u8(i)
packet[5] = u8(total_frags)
packet[6] = u8((u32(frag_len) >> 8) & 0xff)
packet[7] = u8(u32(frag_len) & 0xff)
if frag_len > 0 {
unsafe {
C.memcpy(&packet[8], payload.str + start, frag_len)
}
}
socket.write_to(addr, packet) or { return err }
time.sleep(2 * time.millisecond)
}
}
// read_udp_msg reads packets from a socket and reassembles them into a single string.
// Security boundary: Filters out packets from unexpected addresses during reassembly,
// verifies index boundaries, and checks fragment total counts.
fn read_udp_msg(mut socket net.UdpConn, max_allowed_fragments int) !(string, net.Addr) {
mut fragments := map[int][]u8{}
mut total_frags := -1
mut remote_addr := net.Addr{}
mut buf := []u8{len: 2048}
for {
read, addr := socket.read(mut buf) or { return err }
if read == 0 {
return error('empty packet read')
}
if read < 8 {
return error('packet too small to contain header')
}
if buf[0] != `U` || buf[1] != `D` || buf[2] != `P` || buf[3] != `0` {
return error('invalid packet magic bytes')
}
frag_idx := int(buf[4])
total := int(buf[5])
frag_len := int((u32(buf[6]) << 8) | u32(buf[7]))
if read < 8 + frag_len {
return error('packet payload length mismatch')
}
if total > max_allowed_fragments {
return error('incoming message total fragments ${total} exceeds limit of ${max_allowed_fragments}')
}
if total <= 0 {
return error('invalid total fragments count')
}
if frag_idx < 0 || frag_idx >= total {
return error('invalid fragment index')
}
if total_frags == -1 {
total_frags = total
remote_addr = addr
} else {
// Injection defense: Ignore packets from other addresses during this reassembly
if addr.str() != remote_addr.str() {
continue
}
// Security validation: Mismatched fragment count from client mid-stream
if total != total_frags {
return error('fragment total count mismatch during reassembly')
}
}
fragments[frag_idx] = buf[8..8 + frag_len].clone()
if fragments.len == total_frags {
mut full_payload := []u8{}
for i in 0 .. total_frags {
if i !in fragments {
return error('missing fragment ${i} in reassembly')
}
full_payload << fragments[i]
}
return full_payload.bytestr(), remote_addr
}
}
return error('unexpected read loop termination')
}
// run_server starts the UDP server, processes fragments, reassembles them,
// and echoes back the full message or an error if size is exceeded.
fn run_server(port int) ! {
mut socket := net.listen_udp('127.0.0.1:${port}') or {
println('Server: Failed to listen on port ${port}: ${err}')
return err
}
defer {
socket.close() or {}
}
println('Server: Listening for UDP packets on port ${port}...')
mut reassemblers := map[string]UdpReassembler{}
mut buf := []u8{len: 2048}
for {
// Real-world security pruning: Sweeps stale reassembler states to prevent memory exhaustion DoS
now := time.now()
for key, state in reassemblers {
if now - state.last_seen > 5 * time.second {
reassemblers.delete(key)
}
}
read, addr := socket.read(mut buf) or {
println('Server: Read failed: ${err}')
break
}
if read == 0 {
break
}
if read < 8 {
println('Server: Received packet too small to contain header')
continue
}
// Verify header magic bytes
if buf[0] != `U` || buf[1] != `D` || buf[2] != `P` || buf[3] != `0` {
println('Server: Invalid packet magic bytes')
continue
}
frag_idx := int(buf[4])
total_frags := int(buf[5])
frag_len := int((u32(buf[6]) << 8) | u32(buf[7]))
if read < 8 + frag_len {
println('Server: Packet payload length mismatch')
continue
}
// Real-world safety limit check: Reject if fragment count exceeds threshold (max 5 fragments = 5KB)
max_allowed_fragments := 5
if total_frags > max_allowed_fragments {
println('Server: Rejected message from ${addr}. Total fragments ${total_frags} exceeds limit of ${max_allowed_fragments}.')
// Only send one error packet (on the first fragment index) to avoid flooding the client's socket queue
if frag_idx == 0 {
write_udp_msg_to(mut socket, addr, 'Error: Message size exceeds limit') or {}
}
continue
}
if total_frags <= 0 {
println('Server: Invalid total fragments count ${total_frags}')
continue
}
if frag_idx < 0 || frag_idx >= total_frags {
println('Server: Invalid fragment index ${frag_idx} for total ${total_frags}')
continue
}
addr_str := addr.str()
if addr_str !in reassemblers {
reassemblers[addr_str] = UdpReassembler{
total: total_frags
last_seen: now
}
}
mut r := reassemblers[addr_str]
// Reset state if fragment total count changes mid-stream
if r.total != total_frags {
println('Server: Resetting reassembler for ${addr} due to fragment total count change')
r = UdpReassembler{
total: total_frags
last_seen: now
}
}
r.last_seen = now
r.fragments[frag_idx] = buf[8..8 + frag_len].clone()
if r.fragments.len == r.total {
mut full_payload := []u8{}
mut success := true
for i in 0 .. r.total {
if i !in r.fragments {
success = false
break
}
full_payload << r.fragments[i]
}
// Clean up reassembler state
reassemblers.delete(addr_str)
if success {
message := full_payload.bytestr()
preview_len := if message.len > 30 { 30 } else { message.len }
println('Server received full message from ${addr} (len: ${message.len}): "${message[..preview_len]}"...')
if message == 'Goodbye' {
println('Server received Goodbye. Replying and exiting...')
write_udp_msg_to(mut socket, addr, 'Goodbye!') or {
println('Server: Write failed: ${err}')
}
break
}
response := 'Echo: ${message}'
write_udp_msg_to(mut socket, addr, response) or {
println('Server: Write failed: ${err}')
break
}
}
} else {
reassemblers[addr_str] = r
}
}
println('Server finished.')
}
// run_client connects to the UDP server and runs test cases (small, fragmented, overflow, goodbye).
fn run_client(port int) ! {
mut socket := net.dial_udp('127.0.0.1:${port}') or {
println('Client: Failed to dial server: ${err}')
return err
}
defer {
socket.close() or {}
}
println('Client: Bound to server destination.')
// 1. Send standard small message
msg1 := 'Ping 1'
println('Client sending small message: "${msg1}"')
write_udp_msg(mut socket, msg1)!
resp1, _ := read_udp_msg(mut socket, 5)!
println('Client received response: "${resp1}"')
time.sleep(50 * time.millisecond)
// 2. Send fragmented message within limit (3000 bytes -> 3 fragments)
msg2 := 'A'.repeat(3000)
println('Client sending fragmented message of length ${msg2.len} (3 fragments)...')
write_udp_msg(mut socket, msg2)!
resp2, _ := read_udp_msg(mut socket, 5)!
println('Client received response of length ${resp2.len} successfully!')
time.sleep(50 * time.millisecond)
// 3. Attempt to send message exceeding fragments limit (5500 bytes -> 6 fragments)
msg3 := 'B'.repeat(5500)
println('Client sending large message of length ${msg3.len} (6 fragments)...')
write_udp_msg(mut socket, msg3)!
resp3, _ := read_udp_msg(mut socket, 10)!
println('Client received response for overflow message: "${resp3}"')
time.sleep(50 * time.millisecond)
// 4. Send Goodbye to exit
println('Client sending: "Goodbye"')
write_udp_msg(mut socket, 'Goodbye')!
resp4, _ := read_udp_msg(mut socket, 5)!
println('Client received response: "${resp4}"')
}
fn main() {
println('=== Persistent UDP Protocol Demo ===')
port := 38294
// Spawn the server in a background thread
spawn fn (p int) {
run_server(p) or { println('Server thread failed: ${err}') }
}(port)
// Allow the server thread a short time to start and bind
time.sleep(100 * time.millisecond)
// Run the client in the main thread
run_client(port) or { println('Client failed: ${err}') }
// Give the server a small window to finish deferred cleanups
time.sleep(50 * time.millisecond)
println('UDP Protocol Demo finished.')
}
Deep Dive Explanation: UDP Packet Fragmentation & Reassembly
1. UDP vs. TCP and the MTU Constraint
Unlike TCP, which handles packet streaming and division transparently, UDP is a datagram-oriented protocol. It sends individual, self-contained packets.
- Maximum Packet Sizes: The theoretical maximum size of a UDP packet is 65,535 bytes (including headers), but in practice, any packet larger than the network's MTU (Maximum Transmission Unit) (typically 1500 bytes on ethernet/internet routers) will be fragmented at the IP layer.
- Why IP Fragmentation is Bad: If any single IP fragment is lost during transmission, the entire UDP packet is discarded. This dramatically increases packet loss rates for large payloads.
- Solution: Implement application-level fragmentation. By splitting payloads into smaller chunks (e.g. 1024 bytes), we ensure each chunk fits comfortably inside a single MTU window, minimizing packet drops.
2. The Custom UDP Fragmentation Protocol
This example implements application-level fragmentation and reassembly using a custom 8-byte header prefix:
- Protocol ID (
UDP0): 4 bytes to identify valid application packets. - Fragment Index (1 byte): The sequence index of the current packet (0-indexed).
- Total Fragments (1 byte): The total number of packets that make up the complete message.
- Fragment Length (2 bytes): The size of the payload following this header (max 1024 bytes).
3. Reassembly Mechanics & State Management
Because UDP does not guarantee packet delivery order, packets can arrive out of sequence.
- State Tracking: The server uses a
reassemblersmap, keyed by the client's socket address (addr.str()). This maps each client to its ownUdpReassemblerstructure containing a map of packet indices to their payload bytes. - Sequence Ordering: Once the number of collected fragments matches
total_frags, the server iterates from0tototal_frags - 1to assemble the payload in the correct order, bypassing any network out-of-order delivery issues. - State Cleanup: As soon as a message is successfully reassembled,
reassemblers.delete(addr_str)is called. This frees memory immediately and prevents state leak.
4. Safety & DOS Protections
- Max Fragment Constraints: The server enforces
max_allowed_fragments := 5(equivalent to a maximum total message size of ~5 KB). If a packet arrives claiming a higher fragment count, it is discarded immediately to prevent malicious clients from exhausting server memory by flooding it with un-reassemblable data.
Net Unix
Net Unix
Unix Domain Sockets (UDS) provide a high-performance inter-process communication (IPC) channel on POSIX systems. Unlike standard TCP/UDP networking sockets which transmit data over the network stack (loopback interface), UDS transmits data directly inside the OS kernel, bypassing the IP protocol stack overhead completely. UDS endpoints are bound to filesystem paths (e.g., /tmp/mysocket). V's net.unix module provides listen_stream and connect functions that mimic standard TCP stream sockets, making it easy to build fast local microservices.
This example illustrates cleaning up stale socket files, launching a Unix domain socket listener/server, connecting a client to it, and exchanging messages.
Additional Context from Repository docs:
This example demonstrates Unix domain socket client-server communication using the net.unix module.
module main
import net.unix
import os
import time
// run_server starts the Unix socket server, accepts one client connection,
// echoes back the received message, and exits.
fn run_server(socket_path string) ! {
// Clean up any stale socket file from a previous run
if os.exists(socket_path) {
os.rm(socket_path)!
}
// Listen on the Unix socket path with default options
mut listener := unix.listen_stream(socket_path, unix.ListenOptions{}) or {
println('Server: Failed to listen on ${socket_path}: ${err}')
return err
}
defer {
listener.close() or {}
listener.unlink() or {}
}
println('Server: Listening on socket path: ${socket_path}')
// Accept an incoming connection
mut conn := listener.accept() or {
println('Server: Failed to accept connection: ${err}')
return err
}
defer {
conn.close() or {}
}
println('Server: Client connected!')
// Read client's message
mut buf := []u8{len: 1024}
n := conn.read(mut buf) or {
println('Server: Read failed: ${err}')
return err
}
message := buf[..n].bytestr()
println('Server: Received message: "${message}"')
// Write response back to the client
response := 'Echo: ${message}'
conn.write(response.bytes()) or {
println('Server: Write failed: ${err}')
return err
}
println('Server: Sent echo response.')
}
// run_client connects to the Unix socket server, sends a message,
// reads the echo response, and closes the connection.
fn run_client(socket_path string) ! {
println('Client: Connecting to ${socket_path}...')
mut conn := unix.connect_stream(socket_path) or {
println('Client: Failed to connect: ${err}')
return err
}
defer {
conn.close() or {}
}
println('Client: Connected!')
// Send message to the server
message := 'Hello V Unix Domain Sockets!'
println('Client: Sending message: "${message}"')
conn.write(message.bytes()) or {
println('Client: Write failed: ${err}')
return err
}
// Read server response
mut buf := []u8{len: 1024}
n := conn.read(mut buf) or {
println('Client: Read failed: ${err}')
return err
}
response := buf[..n].bytestr()
println('Client: Received response: "${response}"')
}
fn main() {
println('=== net.unix Module Demo ===')
// Create a unique temporary socket path
socket_path := os.join_path(os.temp_dir(), 'v_unix_socket_example')
// Spawn the server in a background thread
spawn fn (path string) {
run_server(path) or { println('Server thread failed: ${err}') }
}(socket_path)
// Allow the server thread a short time to start and bind
time.sleep(100 * time.millisecond)
// Run the client in the main thread
run_client(socket_path) or { println('Client failed: ${err}') }
// Give the server a small window to finish deferred cleanups
time.sleep(50 * time.millisecond)
println('Unix Sockets Demo finished.')
}
Unix Persistent
This example demonstrates a persistent length-prefixed Unix domain socket connection, processing payloads in chunks, and rejecting messages exceeding safety size boundaries.
module main
import net.unix
import os
import time
const max_message_size = 8192
// write_msg sends a message using a 4-byte magic signature and a 4-byte big-endian length in a single write syscall.
fn write_msg(mut conn unix.StreamConn, payload string) ! {
mut buf := []u8{len: 8 + payload.len}
buf[0] = `M`
buf[1] = `S`
buf[2] = `G`
buf[3] = `0`
buf[4] = u8((u32(payload.len) >> 24) & 0xff)
buf[5] = u8((u32(payload.len) >> 16) & 0xff)
buf[6] = u8((u32(payload.len) >> 8) & 0xff)
buf[7] = u8(u32(payload.len) & 0xff)
if payload.len > 0 {
unsafe {
C.memcpy(&buf[8], payload.str, payload.len)
}
}
// Send consolidated buffer in a single system call
conn.write(buf) or { return err }
}
// read_exact reads exactly `size` bytes from the connection, processing data in chunks.
// Real-world performance optimization: Reads directly into mutable slice views of our pre-allocated
// buffer to achieve zero-allocation reads inside the chunking loop.
fn read_exact(mut conn unix.StreamConn, size int) ![]u8 {
mut data := []u8{len: size}
mut read_bytes := 0
for read_bytes < size {
remaining := size - read_bytes
// Use a small buffer chunk limit (e.g. 512 bytes) to demonstrate reading in chunks
chunk_limit := if remaining > 512 { 512 } else { remaining }
n := conn.read(mut data[read_bytes..read_bytes + chunk_limit]) or { return err }
if n == 0 {
if read_bytes == 0 {
return error('EOF')
}
return error('unexpected end of stream')
}
read_bytes += n
}
return data
}
// read_msg reads a single framed message.
fn read_msg(mut conn unix.StreamConn, max_size int) !string {
// Read header: 4 magic bytes + 4 length bytes = 8 bytes
header_bytes := read_exact(mut conn, 8) or { return err }
// Validate protocol magic bytes
if header_bytes[0] != `M` || header_bytes[1] != `S` || header_bytes[2] != `G`
|| header_bytes[3] != `0` {
return error('invalid protocol magic bytes')
}
// Reconstruct big-endian length
len := int((u32(header_bytes[4]) << 24) | (u32(header_bytes[5]) << 16) | (u32(header_bytes[6]) << 8) | u32(header_bytes[7]))
// Real-world security boundary: Reject messages larger than allowed limit to prevent DoS (OOM)
if len > max_size {
return error('message size ${len} exceeds limit of ${max_size} bytes')
}
if len < 0 {
return error('invalid negative message length')
}
// Read the actual payload
payload_bytes := read_exact(mut conn, len) or { return err }
return payload_bytes.bytestr()
}
// run_server starts the Unix socket server, accepts a connection,
// and processes incoming messages in a loop according to our framing protocol.
fn run_server(socket_path string) ! {
// Clean up any stale socket file from a previous run
if os.exists(socket_path) {
os.rm(socket_path)!
}
// Listen on the Unix socket path
mut listener := unix.listen_stream(socket_path, unix.ListenOptions{}) or {
println('Server: Failed to listen on ${socket_path}: ${err}')
return err
}
defer {
listener.close() or {}
listener.unlink() or {}
}
println('Server: Listening on socket path: ${socket_path}')
mut conn := listener.accept() or {
println('Server: Failed to accept connection: ${err}')
return err
}
defer {
conn.close() or {}
}
println('Server: Client connected!')
// Real-world safety practice: Set read and write timeouts to prevent connection hang-ups (Slowloris DoS)
conn.set_read_timeout(time.second * 5)
conn.set_write_timeout(time.second * 5)
for {
message := read_msg(mut conn, max_message_size) or {
if err.msg() == 'EOF' {
println('Server: Client disconnected cleanly (EOF).')
} else {
println('Server: Connection closed or protocol error: ${err}')
}
break
}
// Preview message content
preview_len := if message.len > 30 { 30 } else { message.len }
println('Server received message (len: ${message.len}): "${message[..preview_len]}"...')
if message == 'Goodbye' {
println('Server received Goodbye. Replying and closing connection...')
write_msg(mut conn, 'Goodbye!') or { println('Server: Write failed: ${err}') }
break
}
response := 'Echo: ${message}'
write_msg(mut conn, response) or {
println('Server: Write failed: ${err}')
break
}
}
println('Server finished.')
}
// run_client connects to the Unix socket server, sends multiple messages (including
// a large chunked message and an invalid/overflow message), and validates responses.
fn run_client(socket_path string) ! {
println('Client: Connecting to ${socket_path}...')
mut conn := unix.connect_stream(socket_path) or {
println('Client: Failed to connect: ${err}')
return err
}
defer {
conn.close() or {}
}
println('Client: Connected!')
// Set connection timeouts for the client too
conn.set_read_timeout(time.second * 5)
conn.set_write_timeout(time.second * 5)
// 1. Send a standard small message
msg1 := 'Ping 1'
println('Client sending small message: "${msg1}"')
write_msg(mut conn, msg1)!
resp1 := read_msg(mut conn, max_message_size)!
println('Client received response: "${resp1}"')
time.sleep(50 * time.millisecond)
// 2. Send a large message within limit (5000 bytes) to trigger chunked read assembly
msg2 := 'A'.repeat(5000)
println('Client sending large message of length ${msg2.len}...')
write_msg(mut conn, msg2)!
resp2 := read_msg(mut conn, max_message_size)!
println('Client received response of length ${resp2.len} successfully!')
time.sleep(50 * time.millisecond)
// 3. Attempt to send an invalid/overflow message (header length > max_message_size)
println('Client sending invalid header claiming 100,000 bytes payload...')
magic := [u8(`M`), `S`, `G`, `0`]
bad_len_bytes := [u8(0), 1, 134, 160] // 100,000 big-endian
conn.write(magic)!
conn.write(bad_len_bytes)!
// The server must reject the message and terminate the connection
mut buf := []u8{len: 1}
n := conn.read(mut buf) or {
println('Client: Successfully verified server rejected overflow and closed connection: ${err}')
return
}
if n == 0 {
println('Client: Successfully verified server rejected overflow (EOF received).')
} else {
println('Client: Warning - Server did not close connection on overflow!')
}
}
fn main() {
println('=== Persistent Unix Sockets Protocol Demo ===')
socket_path := os.join_path(os.temp_dir(), 'v_unix_socket_persistent')
// Spawn the server in a background thread
spawn fn (path string) {
run_server(path) or { println('Server thread failed: ${err}') }
}(socket_path)
// Allow the server thread a short time to start and bind
time.sleep(100 * time.millisecond)
// Run the client in the main thread
run_client(socket_path) or { println('Client failed: ${err}') }
// Give the server a small window to finish deferred cleanups
time.sleep(50 * time.millisecond)
println('Unix Sockets Protocol Demo finished.')
}
Other Stdlib Updates
This section is grouped into focused subtopics so you can jump quickly to the area you need. The examples here were expanded from the runnable standard-library demos under the repository's languageupdatesandstdlib/02standard_library folder and verified by running them with V.
Core Language and Type Features
Standard Library and OS Modules
- Strings Builder
- Os Advanced Io
- Os Operations
- Os Process Pipe
- Os System Info
- Time And Stopwatch
- Http Client
- Regex Matching
- Command Line Flags
- Datatypes Collections
- Gg Graphics
- Command Line Arguments
- Math And Rand
Security, Data, and Formats
- Crypto Asymmetric
- Crypto Entropy
- Crypto Hash
- Crypto Kdf
- Crypto Mac
- Crypto Symmetric
- Log And Crypto
- Encoding Formats
- Arrays Utility
- Toml
- Strconv
- Semver
- Maps Standard Library Module (maps.v)
- Archive Tar
- Compress Deflate
- Compress Gzip
- Compress Szip
- Compress Zlib
- Compress Zstd
- Hash
- Bitfield
Concurrency, CLI, and App Development
I/O, Streams, and Terminal
Options And Results
Options And Results
V has a very rich and growing standard library and is actively updated. This lesson on Options And Results showcases modern standard library packages, system calls, network sockets, inline assembly, or WASM support.
Additional Context from Repository docs:
This example demonstrates the concepts of options and results.
module main
// Result type (!T) is used when a function can return an error.
fn divide(a f64, b f64) !f64 {
if b == 0 {
return error('division by zero')
}
return a / b
}
// Option type (?T) is used when a function can return nothing (none).
fn find_user(id int) ?string {
if id == 1 {
return 'Alice'
}
return none
}
fn main() {
// 1. Handling a Result type with an `or` block
// Inside the `or` block, the special variable `err` is available.
val1 := divide(10.0, 2.0) or {
println('Error: ${err}')
0.0
}
println('Result 1: ${val1}')
// 2. Handling a failed Result
val2 := divide(10.0, 0.0) or {
println('Error: ${err}')
0.0
}
println('Result 2: ${val2}')
// 3. Handling an Option type with an `or` block
// For Option types, the value is unwrapped or the fallback value is returned.
user1 := find_user(1) or { 'Guest' }
println('User 1: ${user1}')
user2 := find_user(99) or { 'Guest' }
println('User 2: ${user2}')
// 4. Using if-let syntax to check Options
if name := find_user(1) {
println('Found user: ${name}')
} else {
println('User not found')
}
}
Generics
Generics
V has a very rich and growing standard library and is actively updated. This lesson on Generics showcases modern standard library packages, system calls, network sockets, inline assembly, or WASM support.
Additional Context from Repository docs:
This example demonstrates the concepts of generics.
module main
// Stack[T] represents a generic stack structure.
struct Stack[T] {
mut:
items []T
}
// push appends an item of type T to the stack.
fn (mut s Stack[T]) push(item T) {
s.items << item
}
// pop removes and returns the top item of type T from the stack,
// or returns `none` (Option type) if the stack is empty.
fn (mut s Stack[T]) pop() ?T {
if s.items.len == 0 {
return none
}
return s.items.pop()
}
// print_val is a generic function that takes any type T and prints it.
fn print_val[T](val T) {
println('Value: ${val}')
}
fn main() {
// 1. Using a generic struct with integers
mut int_stack := Stack[int]{}
int_stack.push(10)
int_stack.push(20)
println('Popped: ${int_stack.pop() or { 0 }}')
println('Popped: ${int_stack.pop() or { 0 }}')
println('Popped from empty stack: ${int_stack.pop() or { -1 }}')
// 2. Using the same generic struct with strings
mut str_stack := Stack[string]{}
str_stack.push('V')
str_stack.push('lang')
println('Popped: ${str_stack.pop() or { 'empty' }}')
println('Popped: ${str_stack.pop() or { 'empty' }}')
// 3. Calling a generic function with different types
print_val[string]('V monomorphizes generics at compile-time!')
print_val[f64](3.14159)
}
Interfaces
Interfaces
V has a very rich and growing standard library and is actively updated. This lesson on Interfaces showcases modern standard library packages, system calls, network sockets, inline assembly, or WASM support.
Additional Context from Repository docs:
This example demonstrates the concepts of interfaces.
module main
// Speaker is an interface. Any struct that implements a `speak() string` method
// implicitly implements Speaker. There is no `implements` keyword.
interface Speaker {
speak() string
}
struct Dog {
name string
}
// speak implements Speaker for Dog
fn (d Dog) speak() string {
return 'Woof! My name is ${d.name}.'
}
struct Cat {
name string
}
// speak implements Speaker for Cat
fn (c Cat) speak() string {
return 'Meow! My name is ${c.name}.'
}
// perform_speak accepts any type implementing the Speaker interface
fn perform_speak(s Speaker) {
println(s.speak())
}
fn main() {
d := Dog{
name: 'Buddy'
}
c := Cat{
name: 'Whiskers'
}
// 1. Passing structs directly to functions expecting an interface
perform_speak(d)
perform_speak(c)
// 2. Creating an array of interfaces
speakers := [Speaker(d), Speaker(c)]
for speaker in speakers {
println('From array: ${speaker.speak()}')
}
}
Sum Types
Sum Types
V has a very rich and growing standard library and is actively updated. This lesson on Sum Types showcases modern standard library packages, system calls, network sockets, inline assembly, or WASM support.
Additional Context from Repository docs:
This example demonstrates the concepts of sum types.
module main
// Define structs for different shapes
struct Circle {
radius f64
}
struct Rectangle {
width f64
height f64
}
struct Triangle {
base f64
height f64
}
// Shape is a Sum Type. A Shape variable can store a Circle, Rectangle, or Triangle.
type Shape = Circle | Rectangle | Triangle
// get_area calculates the area depending on the concrete type stored in Shape.
fn get_area(s Shape) f64 {
// Inside the match branches, the variable is smart-casted to its concrete type.
match s {
Circle {
return 3.14159 * s.radius * s.radius
}
Rectangle {
return s.width * s.height
}
Triangle {
return 0.5 * s.base * s.height
}
}
}
fn main() {
// 1. Creating values of the sum type
shapes := [
Shape(Circle{
radius: 5.0
}),
Shape(Rectangle{
width: 4.0
height: 6.0
}),
Shape(Triangle{
base: 3.0
height: 4.0
}),
]
// 2. Iterating and pattern-matching
for shape in shapes {
match shape {
Circle {
println('Found Circle with radius ${shape.radius}. Area: ${get_area(shape):.2f}')
}
Rectangle {
println('Found Rectangle of ${shape.width}x${shape.height}. Area: ${get_area(shape):.2f}')
}
Triangle {
println('Found Triangle with base ${shape.base} and height ${shape.height}. Area: ${get_area(shape):.2f}')
}
}
}
}
Attributes
Attributes
V has a very rich and growing standard library and is actively updated. This lesson on Attributes showcases modern standard library packages, system calls, network sockets, inline assembly, or WASM support.
Additional Context from Repository docs:
This example demonstrates the concepts of attributes.
module main
import json
// User uses attributes to control JSON field names and to hide a field from encoding.
struct User {
name string @[json: 'username']
age int @[json: 'user_age']
secret string @[json: '-']
}
// Note shows how database-related attributes can describe a schema shape.
struct Note {
id int @[primary; sql: serial]
message string @[sql: 'detail'; unique]
}
// deprecated warns developers when they call this function.
@[deprecated: 'use modern_greet instead']
fn old_greet() {
println('Hello from the old greeting!')
}
// modern_greet is the preferred replacement for old_greet.
fn modern_greet() {
println('Hello from the modern greeting!')
}
// inline hints the compiler that this small function should be inlined.
@[inline]
fn add(a int, b int) int {
return a + b
}
// required marks a function parameter as something that should be supplied explicitly.
@[required]
fn greet_user(name string) string {
return 'Hello, ${name}!'
}
fn main() {
println('=== attributes demo ===')
// Build a User instance and encode it to JSON.
u := User{
name: 'Bob'
age: 30
secret: 'hidden'
}
encoded := json.encode(u)
println('Encoded JSON: ${encoded}')
// Decode a JSON payload that uses the custom field names from the attributes.
decoded := json.decode(User, '{"username":"Alice","user_age":25}') or {
println('JSON error: ${err}')
User{}
}
println('Decoded User -> Name: ${decoded.name}, Age: ${decoded.age}')
// The inline attribute is only a hint, but the example shows the function call.
sum := add(10, 20)
println('Sum: ${sum}')
// Call the modern function and the required-parameter helper.
modern_greet()
println(greet_user('Ada'))
// The Note struct is only used to demonstrate the attribute syntax here.
println('Note schema fields: ${Note{}.id} / ${Note{}.message}')
// Calling old_greet() will compile successfully but output a warning:
// warning: old_greet has been deprecated. use modern_greet instead
// old_greet()
}
Compile-Time Directives & Compile-Time Code
Compile-Time Directives & Compile-Time Code
V provides a powerful set of compile-time (or 'comptime') directives and code features, prefixed with $. These instructions are evaluated and processed by the compiler during compilation, allowing you to optimize code execution, prune unused branches, dynamically query compilation environment properties, and embed assets directly into the final binary.
1. Conditional Compilation (`$if` Condition)
If you want an if expression to be evaluated at compile time, prefix it with $. Inactive branches are excluded from compilation entirely, meaning their type checks still occur but no code is generated for them in the final executable.
- Multiple Conditions: You can combine multiple platforms or build modes in one branch using logic operators (
||,&&). - Expression Usage: A compile-time
$ifcan be used as an expression to conditionally assign values. $else-$ifChains: You can chain compile-time conditions using$else $ifto check against various compilers, platforms, or custom defines.
Builtin `$if` Compilation Target Options
Below is the full list of builtin options supported inside compile-time $if conditions:
| OS target | Compilers | Platforms | Other |
| :----------------------------- | :--------------- | :---------------------------- | :------------------------------------------------------- |
| windows, linux, macos | gcc, tinyc | amd64, arm64, aarch64 | debug, prod, test |
| darwin, ios, bsd | clang, mingw | i386, arm32 | js, glibc, prealloc |
| freebsd, openbsd, netbsd | msvc | rv64, rv32, s390x | no_bounds_checking, freestanding |
| android, mach, dragonfly | cplusplus | ppc64le | no_segfault_handler, no_backtrace |
| gnu, hpux, haiku, qnx | | x64, x32 | no_main, fast_math, apk, threads |
| solaris, termux | | little_endian, big_endian | js_node, js_browser, js_freestanding |
| serenity, vinix, plan9 | | | interpreter, es5, profile, wasm32 |
| | | | wasm32_emscripten, wasm32_wasi, native, autofree |
2. Compile-Time Flag Defines (`$d`)
V allows retrieving custom flag values defined via the command line with -d flag=value or -d flag (which defaults to -d flag=true).
- To fetch the flag inside your code, use:
$d('flag_name', default_value). - The
default_valueacts as a fallback when the flag is not provided on the command line. It must be a pure literal: booleans (true/false), integers (0), floats (0.0), strings ('string'), or runes (\v\``). - You can also use
$d('flag_name', false)inside$ifconditions (e.g.$if $d('my_flag', false) { ... }) to selectively enable or disable blocks of code. $dcan also be used in top-level statements like#flagand#include(e.g.,#flag linux -I $d('my_include', '/usr')/include).
3. Compile-Time Warnings & Errors
You can generate custom compile-time messages to warn the developer or abort the build:
$compile_warn('message')prints a warning during compilation but allows the build to continue.$compile_error('message')immediately halts compilation and prints a custom error.
These are particularly powerful when combined with platform target checks to enforce compatibility (e.g., aborting compilation on unsupported architectures).
4. Environment Variables (`$env`)
$env('VAR_NAME') retrieves the value of an environment variable at compilation time and embeds it as a string literal. It can also be used inside top-level #flag and #include statements.
5. File Asset Embedding (`$embed_file`)
V can embed the raw content of any external file directly inside the compiled binary using $embed_file('path').
- Returns an
EmbedFileDatastructure. Use.to_string()or.to_bytes()to retrieve contents. - In production builds (
-prod),$embed_filesupports optional on-the-fly compression via.zlib(e.g.$embed_file('x.css', .zlib)). - For local development ease, compile with
-d embed_only_metadata. The file won't be embedded, and V will load the file from disk the first timedata()is called, permitting external live edits without recompiling.
6. Compile-Time Templates (`$tmpl`)
$tmpl('path/to/template.html') compiles and parses a simple template file, interpolating any variables (prefixed with @ in the template) that exist in the calling scope.
Here is a comprehensive code example highlighting all compile-time directives and code features in action:
module main
fn main() {
println('=== V Compile-Time Directives & Code Demo ===')
// 1. Conditional Compilation ($if) and multiple conditions
println('\n--- 1. Conditional Compilation (compile-time \$if) ---')
$if macos {
println('OS target: macOS')
}
$if windows {
println('OS target: Windows')
}
$if linux {
println('OS target: Linux')
}
// Multiple conditions in one branch
$if ios || android {
println('Target platform is a mobile device (iOS/Android).')
} $else $if macos || linux || windows {
println('Target platform is a desktop OS.')
}
$if linux && x64 {
println('Running specifically on 64-bit Linux.')
}
// 2. $if as an expression
println('\n--- 2. \$if Used as an Expression ---')
os_family := $if windows { 'Windows' } $else { 'Unix-like' }
println('OS Family expression: ${os_family}')
// 3. $else-$if compiler branches
println('\n--- 3. Compiler Type Detection (\$else-\$if) ---')
$if tinyc {
println('Compiled with: TinyC')
} $else $if clang {
println('Compiled with: Clang')
} $else $if gcc {
println('Compiled with: GCC')
} $else $if msvc {
println('Compiled with: MSVC')
} $else {
println('Compiled with a different/unspecified compiler')
}
// 4. Custom Compile-time Flag defines ($d) with defaults
println('\n--- 4. Compile-Time Flags (\$d) with Default Values ---')
// $d brings values defined via compiler flags (-d flag=val or -d flag)
// Default value must be a pure literal (boolean, int, float, string, or rune)
custom_str := $d('custom_str', 'Default Text')
custom_bool := $d('custom_bool', false)
custom_int := $d('custom_int', 42)
custom_float := $d('custom_float', 3.14159)
custom_char := $d('custom_char', `v`)
println('custom_str: ${custom_str}')
println('custom_bool: ${custom_bool}')
println('custom_int: ${custom_int}')
println('custom_float: ${custom_float}')
println('custom_char: ${rune(custom_char)}')
// We can also use $d('ident', false) inside $if condition to conditionally enable/disable code:
$if $d('enable_feature', false) {
println('Special feature is ENABLED at compile-time!')
} $else {
println('Special feature is DISABLED (default). Compile with `v -d enable_feature run directives.v` to enable.')
}
// 5. Compile-time custom errors and warnings
println('\n--- 5. Compile-Time Errors and Warnings (\$compile_error, \$compile_warn) ---')
// These only trigger if the enclosing $if branch is active/evaluated at compile time.
$if $d('trigger_error', false) {
$compile_error('Explicit compile-time error triggered')
}
$if $d('trigger_warn', false) {
$compile_warn('Explicit compile-time warning triggered')
}
println('No compile-time errors/warnings triggered during this compilation run.')
// 6. $env reads environment values while the program is being compiled.
println('\n--- 6. Compile-Time Environment (compile-time env) ---')
compile_path := $env('PATH')
println('PATH length at compile-time: ${compile_path.len} bytes')
// 7. $embed_file stores a file's contents inside the compiled binary.
println('\n--- 7. Asset Embedding (compile-time embed) ---')
embedded_file := $embed_file('temp_embed.txt')
content := embedded_file.to_string()
println('Embedded File Content:')
println(content)
// 8. $tmpl renders a template file and injects the current variables.
println('\n--- 8. Template Interpolation (compile-time template) ---')
name := 'Developer'
status := 'active'
rendered_template := $tmpl('template.html')
println('Rendered Template Output:')
println(rendered_template)
}
Strings Builder
Strings Builder
V has a very rich and growing standard library and is actively updated. This lesson on Strings Builder showcases modern standard library packages, system calls, network sockets, inline assembly, or WASM support.
Additional Context from Repository docs:
This example demonstrates the concepts of strings builder.
module main
import strings
fn main() {
// 1. Initialize a new Builder with pre-allocated buffer size (e.g. 100 bytes).
// Pre-allocation is highly recommended for performance to reduce memory allocations.
mut sb := strings.new_builder(100)
// 2. Write strings and runes to the buffer
sb.write_string('Welcome ')
sb.write_string('to ')
sb.write_string('the V standard library!')
sb.write_rune(`\n`)
sb.write_string('V is:\n')
features := ['Fast', 'Simple', 'Statically Typed', 'Safe']
for feature in features {
sb.write_string('- ')
sb.write_string(feature)
sb.write_rune(`\n`)
}
// 3. Extract the final constructed string
result := sb.str()
println(result)
// 4. Reset/Clear the builder to reuse it
// In V, `clear()` clears the builder's buffer.
sb.clear()
sb.write_string('New content in builder.')
println(sb.str())
}
Os Advanced Io
This example demonstrates advanced Unix file behaviors such as raw struct binary serialization, cursor seeking (seek/tell), file size truncation, and recursive directory tree traversal.
module main
import os
struct Config {
mut:
id int
val f64
name [20]u8 // fixed-size array of bytes for safe serialization
}
fn main() {
println('=== V Advanced File I/O & Directory Walking ===')
file_path := 'temp_advanced_io.bin'
// --- 1. Struct Reading & Writing (Binary Serialization) ---
println('\n--- 1. Struct Binary Serialization ---')
// Create a mutable file in write/read mode
mut f := os.open_file(file_path, 'w+') or {
println('Failed to open file: ${err}')
return
}
mut cfg := Config{
id: 101
val: 99.99
name: [u8(0), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]!
}
// Populate name
name_str := 'V-OS-Advanced-IO'
for i in 0 .. name_str.len {
if i < 20 {
cfg.name[i] = name_str[i]
}
}
// Write struct representation directly to the file
f.write_struct(cfg) or { println('Failed to write struct: ${err}') }
println('Struct successfully serialized to file.')
// --- 2. Seeking & Cursor Position (seek/tell) ---
println('\n--- 2. File Seeking & Cursor Position ---')
// Retrieve current position in the file (should be size of struct)
pos := f.tell() or { 0 }
println('Current file cursor position: ${pos} bytes')
// Seek back to the beginning of the file (.start)
println('Seeking back to the start of the file...')
f.seek(0, .start) or { println('Failed to seek: ${err}') }
// Read struct back from file
mut read_cfg := Config{
name: [u8(0), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]!
}
f.read_struct(mut read_cfg) or { println('Failed to read struct: ${err}') }
// Extract string from fixed-size byte array
mut bytes := []u8{}
for b in read_cfg.name {
if b == 0 {
break
}
bytes << b
}
name_read := bytes.bytestr()
println('Deserialized Struct:')
println(' ID: ${read_cfg.id}')
println(' Val: ${read_cfg.val}')
println(' Name: ${name_read}')
f.close()
// --- 3. Truncating Files ---
println('\n--- 3. File Truncation (truncate) ---')
// Note: V's os.truncate opens the file with O_TRUNC, resetting it first before sizing.
// Shrinking/sizing a file directly using os.truncate:
println('Truncating file "${file_path}" to 10 bytes...')
os.truncate(file_path, 10) or { println('Failed to truncate: ${err}') }
println('File size after truncation: ${os.file_size(file_path)} bytes')
// Clean up binary file
os.rm(file_path) or {}
// --- 4. Recursive Directory Tree Walking ---
println('\n--- 4. Directory Tree Walking (walk) ---')
// Create a dummy tree for traversal
walk_root := 'temp_walk_root'
sub_dir := os.join_path(walk_root, 'docs')
os.mkdir_all(sub_dir) or {}
os.write_file(os.join_path(walk_root, 'file1.txt'), 'content1') or {}
os.write_file(os.join_path(sub_dir, 'file2.log'), 'content2') or {}
os.write_file(os.join_path(sub_dir, 'file3.txt'), 'content3') or {}
// Recursive walk using a callback
println('Recursive walk using os.walk (all files):')
os.walk(walk_root, fn (path string) {
println(' Found file: ${path}')
})
// Walk with file extension filter
println('Walk with file extension filter using os.walk_ext (.txt only):')
txt_files := os.walk_ext(walk_root, '.txt', os.WalkParams{})
for path in txt_files {
println(' Found .txt file: ${path}')
}
// Cleanup directory tree
os.rmdir_all(walk_root) or {}
println('Directory tree cleanup complete.')
}
Os Operations
Os Operations
V's standard library provides a rich set of cross-platform functions for interacting with the operating system through the os module. Here is the simplest, most practical guide to when you should actually use each of these functions in real-world programming.
1. Basic File Operations
- The Vibe: The "Standard file cabinets."
- What it does: Writes, reads, and checks the existence of files using simple string and byte helpers.
- Best to use when: You need to dump text, save config settings, or read small files quickly.
- Real-world example: Writing user session logs or reading a local settings file.
2. Directory Tree Operations
- The Vibe: The "Digital folder builder."
- What it does: Creates nested folders (
mkdir_all) or removes them (rmdir_all). - Best to use when: You need to construct file paths for organized data storage.
- Real-world example: Creating a new user cache folder like
cache/images/temp/.
3. Path Manipulation & Extraction
- The Vibe: The "Path dissection tool."
- What it does: Extracts directory paths, base names, file extensions, and normalizes them.
- Best to use when: You have a file path and want to rename the file or get its extension without manual string parsing.
- Real-world example: Checking if an uploaded file has a
.jpgextension.
4. Environment & Command Execution
- The Vibe: "Talking to the host machine."
- What it does: Reads system environment variables and runs shell commands.
- Best to use when: You need to fetch config keys (like API tokens) or execute external utilities (like running
git version). - Real-world example: Fetching the
HOMEdirectory to locate user configuration files.
5. File Permissions & Ownership (Chmod/Chown)
- The Vibe: The "Keymaster/security guard."
- What it does: Alters who can read/write/execute a file and changes user/group owner IDs.
- Best to use when: Making a script executable or securing sensitive credentials.
- Real-world example: Restricting a database file's permissions to be readable only by the owner (
chmod 0o600).
6. Globbing
- The Vibe: The "Wildcard detector."
- What it does: Matches a list of files using wildcard patterns (like
*.txt). - Best to use when: You need to process a batch of files matching a name template.
- Real-world example: Storing files matching
log_*.txtand deleting them in a batch.
Additional Context from Repository docs:
This example demonstrates the concepts of OS operations.
module main
import os
fn main() {
filename := 'temp_book_example.txt'
content := 'V standard library makes OS operations very straightforward.'
// ==========================================
// 1. Basic File Operations (Writing, Reading, Existence)
// ==========================================
// os.write_file writes a string to a file. It overwrites the file if it already exists.
// We handle errors using V's explicit "or" block.
println('Writing text to ${filename}...')
os.write_file(filename, content) or {
println('Failed to write file: ${err}')
return
}
// os.exists checks if a file or directory exists at the given path.
if os.exists(filename) {
println('Confirmed: File exists.')
}
// os.read_file reads the entire content of a file and returns it as a string.
read_content := os.read_file(filename) or {
println('Failed to read file: ${err}')
return
}
println('Read content from file: "${read_content}"')
// os.write_lines writes an array of strings to a file, separating them with newlines.
lines := ['Line 1: V has simple OS functions.', 'Line 2: Supporting multiple lines.']
lines_file := 'temp_lines_example.txt'
os.write_lines(lines_file, lines) or { println('Failed to write lines: ${err}') }
// os.read_lines reads a file line-by-line and returns an array of strings.
read_lines := os.read_lines(lines_file) or {
println('Failed to read lines: ${err}')
[]
}
println('Read lines: ${read_lines}')
os.rm(lines_file) or {}
// os.write_bytes and os.read_bytes handle raw binary byte arrays.
// os.file_last_mod_unix retrieves the Unix timestamp of when the file was last modified.
// os.is_file returns true if the path points to a file (not a directory).
bytes_file := 'temp_bytes_example.bin'
os.write_bytes(bytes_file, 'V handles raw bytes.'.bytes()) or {
println('Failed to write bytes: ${err}')
}
read_bytes := os.read_bytes(bytes_file) or { []u8{} }
println('Read bytes: "${read_bytes.bytestr()}"')
println('Last modified time (epoch): ${os.file_last_mod_unix(bytes_file)}')
println('Is a file? ${os.is_file(bytes_file)}')
os.rm(bytes_file) or {}
// os.create creates a new empty file for writing and returns a File handle.
// os.open_append opens an existing file or creates one, positioning the cursor at the end to append data.
// os.open opens an existing file in read-only mode.
handle_file := 'temp_handle_example.txt'
mut f_create := os.create(handle_file) or { panic(err) }
f_create.write_string('Line 1 from file handle\n') or {}
f_create.close()
mut f_append := os.open_append(handle_file) or { panic(err) }
f_append.write_string('Line 2 appended\n') or {}
f_append.close()
mut f_read := os.open(handle_file) or { panic(err) }
mut buf := []u8{len: 100}
n_read := f_read.read(mut buf) or { 0 }
println('Content via file handle:\n${buf[..n_read].bytestr().trim_space()}')
f_read.close()
os.rm(handle_file) or {}
// os.ls returns a list of file and directory names inside the target directory path.
println('Listing files in current directory:')
files := os.ls('.') or {
println('Failed to list directory: ${err}')
[]
}
for file in files {
if file == filename {
println('- Found file: ${file}')
}
}
// os.getenv retrieves the value of a system environment variable.
home_dir := os.getenv('HOME')
println('User HOME directory: ${home_dir}')
// os.exists_in_system_path checks if a command binary is present in the system's PATH.
if os.exists_in_system_path('git') {
println('Confirmed: Git executable exists in system PATH.')
}
// os.execute runs a system command in a shell and returns a Result struct.
// The Result contains both the command exit_code and stdout/stderr output.
println('Running command "uname"...')
res := os.execute('uname')
if res.exit_code == 0 {
println('Operating System: ${res.output.trim_space()}')
} else {
println('Command execution failed with code ${res.exit_code}: ${res.output}')
}
// ==========================================
// 2. Directory Tree Operations
// ==========================================
println('\n--- Directory Tree Operations ---')
// os.mkdir_all recursively creates a full nested directory path (similar to mkdir -p).
nested_dir := os.join_path('temp_parent', 'temp_child')
println('Creating nested directory structure: ${nested_dir}...')
os.mkdir_all(nested_dir) or { println('Failed to create directory structure: ${err}') }
// os.mkdir creates a single new directory.
// os.is_dir checks if a path points to a directory.
// os.is_dir_empty checks if the directory has no files or subfolders.
// os.rmdir deletes a single empty directory.
single_dir := 'temp_single_dir'
os.mkdir(single_dir) or { println('Failed to create directory: ${err}') }
println('Is directory? ${os.is_dir(single_dir)}')
println('Is empty? ${os.is_dir_empty(single_dir)}')
os.rmdir(single_dir) or { println('Failed to remove directory: ${err}') }
// ==========================================
// 3. Path Manipulation & Extraction
// ==========================================
println('\n--- Path Manipulation & Extraction ---')
sample_path := '/usr/local/bin/v.exe'
// Path parsing helpers:
// os.dir returns the parent directory.
// os.base returns the last element of the path.
// os.file_ext returns the file suffix including dot.
// os.file_name returns the filename without the path.
// os.is_abs_path checks if the path starts with root.
// os.real_path resolves symlinks and relative references to return the absolute canonical path.
// os.norm_path cleans up and normalizes path separators.
// os.split_path splits a path into (dir, file_name, file_extension).
println('Sample path: ${sample_path}')
println('Directory: ${os.dir(sample_path)}')
println('Base name: ${os.base(sample_path)}')
println('Extension: ${os.file_ext(sample_path)}')
println('File name: ${os.file_name(sample_path)}')
println('Is absolute? ${os.is_abs_path(sample_path)}')
println('Real path: ${os.real_path('.')}')
println('Norm path: ${os.norm_path('/usr/local/../bin/v')}')
p_dir, p_file, p_ext := os.split_path(sample_path)
println('Split path -> dir: ${p_dir}, file: ${p_file}, ext: ${p_ext}')
// ==========================================
// 4. Working Directory Traversal
// ==========================================
println('\n--- Working Directory Traversal ---')
// os.getwd returns the current active working directory.
// os.chdir changes the current active working directory.
original_wd := os.getwd()
println('Original working directory: ${original_wd}')
println('Changing directory to: temp_parent...')
os.chdir('temp_parent') or { println('Failed to change directory: ${err}') }
println('New working directory: ${os.getwd()}')
// Restore original working directory
os.chdir(original_wd) or { println('Failed to restore directory: ${err}') }
// ==========================================
// 5. Advanced File Operations (Copying, Moving)
// ==========================================
println('\n--- Advanced File Actions ---')
copied_file := 'temp_book_copy.txt'
moved_file := 'temp_book_moved.txt'
// os.cp copies a file from source to destination.
println('Copying ${filename} to ${copied_file}...')
os.cp(filename, copied_file) or { println('Failed to copy file: ${err}') }
// os.mv moves or renames a file.
println('Moving ${copied_file} to ${moved_file}...')
os.mv(copied_file, moved_file) or { println('Failed to move file: ${err}') }
// ==========================================
// 6. Symbolic Links & Nix-Specific Operations
// ==========================================
println('\n--- Nix-Specific Operations ---')
symlink_name := 'temp_book_link.txt'
// os.symlink creates a symbolic link pointing to a target file.
// os.is_link checks if the path points to a symbolic link.
println('Creating symbolic link from ${moved_file} to ${symlink_name}...')
os.symlink(moved_file, symlink_name) or { println('Failed to create symlink: ${err}') }
if os.is_link(symlink_name) {
println('Confirmed: ${symlink_name} is a symbolic link.')
}
// os.chmod changes permission bits on a file (using octal representation).
// os.is_readable, os.is_writable, os.is_executable check specific accessibility bits.
println('Setting file permissions to 0o644 (read/write for owner, read-only for others)...')
os.chmod(moved_file, 0o644) or { println('Failed to change permissions: ${err}') }
println('Is readable? ${os.is_readable(moved_file)}')
println('Is writable? ${os.is_writable(moved_file)}')
println('Is executable? ${os.is_executable(moved_file)}')
// os.getuid and os.getgid get current user and group IDs.
// os.chown changes the user and group owner IDs on a file.
uid := os.getuid()
gid := os.getgid()
println('Setting ownership of ${moved_file} to UID: ${uid}, GID: ${gid}...')
os.chown(moved_file, uid, gid) or { println('Failed to change ownership: ${err}') }
// ==========================================
// 7. File Globbing (glob)
// ==========================================
println('\n--- File Globbing ---')
// os.glob finds all files matching a wildcard pattern (e.g. *.txt).
os.write_file('glob_test_1.txt', '1') or {}
os.write_file('glob_test_2.txt', '2') or {}
globbed_files := os.glob('glob_test_*.txt') or { [] }
println('Glob results: ${globbed_files}')
os.rm('glob_test_1.txt') or {}
os.rm('glob_test_2.txt') or {}
// ==========================================
// 8. Cleanup
// ==========================================
println('\n--- Cleanup ---')
// os.rm deletes a file.
// os.rmdir_all recursively removes a directory and all of its contents.
os.rm(filename) or { println('Failed to remove ${filename}: ${err}') }
os.rm(moved_file) or { println('Failed to remove ${moved_file}: ${err}') }
os.rm(symlink_name) or { println('Failed to remove symlink ${symlink_name}: ${err}') }
os.rmdir_all('temp_parent') or { println('Failed to remove temp_parent directory: ${err}') }
println('Cleanup completed successfully.')
}
Os Process Pipe
This example demonstrates managing subprocesses asynchronously using os.Process, exchanging data via stdin/stdout redirection, passing custom environments, sending POSIX signals (SIGSTOP, SIGCONT, SIGTERM), creating low-level descriptor pipes, and capturing stdout/stderr dynamically via IOCapture.
module main
import os
import time
fn main() {
println('=== V OS Processes, Pipes & Signals (POSIX/Nix) ===')
// --- 1. Spawning and Controlling Processes ---
println('\n--- 1. Asynchronous Child Process (Process) ---')
// Spawning '/bin/cat' as a child process
mut p := os.new_process('/bin/cat')
p.set_args([])
p.set_environment({
'CUSTOM_ENV_VAR': 'V-OS-Demo'
})
// Enable standard I/O redirection to interact with the process
p.set_redirect_stdio()
p.use_stdio_ctl = true
// Start the process asynchronously
p.run()
println('Child process spawned with PID: ${p.pid}')
println('Is alive? -> ${p.is_alive()}')
// Write to the process's standard input
p.stdin_write('Line 1: Hello from the parent process!\n')
p.stdin_write('Line 2: WebAssembly and V standard libraries rule.\n')
// Allow child process buffer to receive and echo the lines
time.sleep(100 * time.millisecond)
// Read output currently available in the stdout pipe
output := p.stdout_read()
println('Read from child stdout:\n${output.trim_space()}')
// --- 2. POSIX Signaling ---
println('\n--- 2. POSIX Signals ---')
// Suspend the child process (SIGSTOP)
println('Suspending child process (SIGSTOP)...')
p.signal_stop()
time.sleep(50 * time.millisecond)
// Resume the child process (SIGCONT)
println('Resuming child process (SIGCONT)...')
p.signal_continue()
time.sleep(50 * time.millisecond)
// Terminate the child process (SIGTERM)
println('Terminating child process (SIGTERM)...')
p.signal_term()
p.wait()
println('Child process exited with status: ${p.status} (Code: ${p.code})')
p.close()
// --- 3. Pipes ---
println('\n--- 3. Low-Level Descriptor Pipes (Pipe) ---')
// Create a new pipe
mut my_pipe := os.pipe() or {
println('Failed to create pipe: ${err}')
return
}
// Write to the pipe
pipe_msg := 'IPC via Pipe'.bytes()
written := my_pipe.write(pipe_msg) or {
println('Failed to write to pipe: ${err}')
0
}
println('Wrote ${written} bytes to pipe.')
// Read from the pipe
mut pipe_buf := []u8{len: 32}
bytes_read := my_pipe.read(mut pipe_buf) or {
println('Failed to read from pipe: ${err}')
0
}
println('Read message from pipe: "${pipe_buf[..bytes_read].bytestr()}"')
my_pipe.close()
// --- 4. Capture Stdout/Stderr ---
println('\n--- 4. Capture Stdout and Stderr (IOCapture) ---')
// Flush stdout to prevent capturing existing print statements
os.flush()
// Capture all stdout/stderr output within this block
mut cap := os.stdio_capture() or {
println('Failed to initialize capture: ${err}')
return
}
// Anything printed here will be redirected to the capture buffer
print('Captured standard output data.')
eprint('Captured standard error data.')
// Restore standard streams and retrieve captured data
captured_out, captured_err := cap.finish()
println('Captured stdout lines: ${captured_out}')
println('Captured stderr lines: ${captured_err}')
}
Os System Info
This example demonstrates calling system diagnostics (os.uname), retrieving current host/user identities, assessing disk capacity and usage metrics (os.disk_usage), and parsing detailed file metadata using POSIX stat/lstat mappings (os.Stat and os.FileMode).
module main
import os
fn main() {
println('=== V OS System & File Information (POSIX/Nix) ===')
// --- 1. System Info (uname, hostname, loginname) ---
println('\n--- 1. System Diagnostics ---')
// os.uname() returns kernel details, release, OS name, and architecture
u := os.uname()
println('Operating System: ${u.sysname}')
println('Node Name (Network): ${u.nodename}')
println('Kernel Release: ${u.release}')
println('Kernel Version: ${u.version}')
println('Machine Architecture: ${u.machine}')
// Retrieve hostname and login user name
host := os.hostname() or { 'unknown_host' }
user := os.loginname() or { 'unknown_user' }
println('Hostname: ${host}')
println('Login Name: ${user}')
// V standard user/system directories
println('User OS: ${os.user_os()}')
println('Home Directory: ${os.home_dir()}')
println('Temp Directory: ${os.temp_dir()}')
println('Config Directory: ${os.config_dir() or { 'N/A' }}')
println('Cache Directory: ${os.cache_dir()}')
println('Data Directory: ${os.data_dir()}')
// Executable details
println('Current Executable: ${os.executable()}')
println('Git Abs Path: ${os.find_abs_path_of_executable('git') or { 'not found' }}')
// Optional environment access & full environment map
println('Home via getenv_opt: ${os.getenv_opt('HOME') or { 'not set' }}')
env_map := os.environ()
// Safely print first few environment keys if available
limit := if env_map.len < 3 { env_map.len } else { 3 }
println('Sample Env Keys: ${env_map.keys()[..limit]}')
// --- 2. Identity and Process Metrics ---
println('\n--- 2. User/Group IDs & Process Context ---')
// Real and effective UID/GIDs
println('User ID (UID): ${os.getuid()}')
println('Group ID (GID): ${os.getgid()}')
println('Effective UID (EUID): ${os.geteuid()}')
println('Effective GID (EGID): ${os.getegid()}')
// Current Process ID and Parent Process ID
println('Process ID (PID): ${os.getpid()}')
println('Parent PID (PPID): ${os.getppid()}')
// --- 3. Disk Space Usage ---
println('\n--- 3. Disk Space Stats ---')
// Query disk space information for the current directory
du := os.disk_usage('.') or {
println('Failed to retrieve disk usage: ${err}')
return
}
// Convert u64 bytes to Gigabytes for user readability
total_gb := f64(du.total) / (1024.0 * 1024.0 * 1024.0)
avail_gb := f64(du.available) / (1024.0 * 1024.0 * 1024.0)
used_gb := f64(du.used) / (1024.0 * 1024.0 * 1024.0)
println('Disk Total: ${total_gb:.2f} GB')
println('Disk Available: ${avail_gb:.2f} GB')
println('Disk Used: ${used_gb:.2f} GB')
// --- 4. Detailed File Metadata (stat/lstat) ---
println('\n--- 4. File Metadata via stat ---')
// Let's create a temporary file to run stat on
temp_file := 'temp_stat_test.txt'
os.write_file(temp_file, 'V stat demo content.') or { return }
// Fetch file stats
st := os.stat(temp_file) or {
println('Failed to stat file: ${err}')
os.rm(temp_file) or {}
return
}
println('File Size: ${st.size} bytes')
println('Inode Number: ${st.inode}')
println('Hard Links Count: ${st.nlink}')
println('Device ID: ${st.dev}')
println('Owner UID: ${st.uid}')
println('Owner GID: ${st.gid}')
// Access access, modify, and status change timestamps
println('Last Access Time: ${st.atime} (Unix Epoch)')
println('Last Modify Time: ${st.mtime} (Unix Epoch)')
println('Last Change Time: ${st.ctime} (Unix Epoch)')
// File type and permissions from Stat
file_type := st.get_filetype()
file_mode := st.get_mode()
println('File Type: ${file_type}') // e.g., regular, directory, link, etc.
println('File Mode Bitmask: 0o${file_mode.bitmask():o}') // octal representation
// Permissions breakdown
println('Permissions -> Owner: R=${file_mode.owner.read} W=${file_mode.owner.write} X=${file_mode.owner.execute}')
println(' Group: R=${file_mode.group.read} W=${file_mode.group.write} X=${file_mode.group.execute}')
println(' Other: R=${file_mode.others.read} W=${file_mode.others.write} X=${file_mode.others.execute}')
// Cleanup
os.rm(temp_file) or {}
}
Time And Stopwatch
Time And Stopwatch
V's standard library provides a robust and precise set of utilities for time retrieval, formatting, parsing, timezone management, and execution timing via the time module. Here is the simplest, most practical guide to when you should actually use each of these tools in real-world programming.
1. Time Retrieval & Fields
- The Vibe: "Checking your wristwatch."
- What it does: Gets the exact current date, time, and nanoseconds.
- Best to use when: You need to timestamp actions or log events.
- Real-world example: Recording when a user logs in.
2. Time Arithmetic & Comparisons
- The Vibe: "Time-traveling and deadlines."
- What it does: Adds or subtracts intervals (days, hours, seconds) and compares which time comes first.
- Best to use when: You need to calculate expirations or duration differences.
- Real-world example: Setting a user token to expire in 2 hours.
3. String Formatting & RFC Standards
- The Vibe: "The translator for calendar dates."
- What it does: Converts raw timestamps into clean, human-readable formats, custom strings, or RFC 3339 standards.
- Best to use when: Displaying dates to users or sending structured time JSON over APIs.
- Real-world example: Printing a post publish date as
YYYY-MM-DD HH:mm:ss.
4. Timezone Conversions (Local & UTC)
- The Vibe: "The jet-lag cure."
- What it does: Converts times between local system time and UTC.
- Best to use when: You store timestamps in UTC (best practice) but need to show them in the user's local timezone.
- Real-world example: Normalizing database entries to UTC time.
5. Relative Time
- The Vibe: "Social media date labels."
- What it does: Formats dates relative to now (e.g., "5 minutes ago", "yesterday").
- Best to use when: Displaying activity feeds or notification boards.
- Real-world example: Showing how long ago a comment was posted.
6. Stopwatch
- The Vibe: "The performance racing timer."
- What it does: Measures sub-millisecond elapsed durations.
- Best to use when: Benchmarking code speed or tracking long-running tasks.
- Real-world example: Measuring how long an API database query took to run.
Additional Context from Repository docs:
This example demonstrates the concepts of time and stopwatch.
module main
import time
fn main() {
println('Time API examples')
println('=================')
// time.now() returns the current system time.
// We can access properties like year, month, day, hour, etc.
now := time.now()
println('Current time: ${now}')
println('Fields -> year=${now.year}, month=${now.month}, day=${now.day}, hour=${now.hour}, minute=${now.minute}, second=${now.second}, nanosecond=${now.nanosecond}, is_local=${now.is_local}')
// ==========================================
// Arithmetic and comparisons
// ==========================================
// now.add() adds a duration to the timestamp.
// now.add_days() adds a specified number of days.
// now.add_seconds() adds a specified number of seconds.
// We can compare Time objects using <, >, ==, and subtract them to get a Duration.
future := now.add(2 * time.hour)
tomorrow := now.add_days(1)
in_30_seconds := now.add_seconds(30)
println('add: ${future}')
println('add_days: ${tomorrow}')
println('add_seconds: ${in_30_seconds}')
println('comparison: now < future -> ${now < future}')
println('comparison: now == now -> ${now == now}')
println('difference: future - now -> ${future - now}')
// ==========================================
// Formatting helpers
// ==========================================
// now.clean() formats time as YYYY-MM-DD HH:MM:SS.
// now.clean12() formats time using a 12-hour clock with AM/PM.
// now.custom_format() formats time using a custom layout pattern.
// now.format() and format_rfc3339() print standard ISO/RFC timestamps.
// format_ss methods print time down to micro, milli, or nanoseconds.
// strftime() uses C-like format specifiers.
println('clean: ${now.clean()}')
println('clean12: ${now.clean12()}')
println('custom_format: ${now.custom_format('YYYY-MM-DD HH:mm:ss')}')
println('format: ${now.format()}')
println('format_rfc3339: ${now.format_rfc3339()}')
println('format_rfc3339_micro: ${now.format_rfc3339_micro()}')
println('format_rfc3339_nano: ${now.format_rfc3339_nano()}')
println('format_ss: ${now.format_ss()}')
println('format_ss_micro: ${now.format_ss_micro()}')
println('format_ss_milli: ${now.format_ss_milli()}')
println('format_ss_nano: ${now.format_ss_nano()}')
println('strftime: ${now.strftime('%Y-%m-%d %H:%M:%S')}')
println('get_fmt_str: ${now.get_fmt_str(time.FormatDelimiter.hyphen, time.FormatTime.hhmm24,
time.FormatDate.yyyymmdd)}')
println('get_fmt_date_str: ${now.get_fmt_date_str(time.FormatDelimiter.hyphen, time.FormatDate.yyyymmdd)}')
println('get_fmt_time_str: ${now.get_fmt_time_str(time.FormatTime.hhmm24)}')
// ==========================================
// Date and time helpers
// ==========================================
// Extra details like day_of_week(), days_from_unix_epoch(), week_of_year(), smonth(), etc.
println('day_of_week: ${now.day_of_week()}')
println('days_from_unix_epoch: ${now.days_from_unix_epoch()}')
println('ddmmy: ${now.ddmmy()}')
println('hhmm: ${now.hhmm()}')
println('hhmm12: ${now.hhmm12()}')
println('hhmmss: ${now.hhmmss()}')
println('long_weekday_str: ${now.long_weekday_str()}')
println('md: ${now.md()}')
println('smonth: ${now.smonth()}')
println('weekday_str: ${now.weekday_str()}')
println('week_of_year: ${now.week_of_year()}')
println('year_day: ${now.year_day()}')
println('ymmdd: ${now.ymmdd()}')
// ==========================================
// UTC and local conversions
// ==========================================
// Convert between UTC and the system local timezone.
// unix(), unix_milli(), etc. return timestamps since the Unix Epoch.
println('is_utc: ${now.is_utc()}')
println('as_local: ${now.as_local()}')
println('as_utc: ${now.as_utc()}')
println('local: ${now.local()}')
println('local_to_utc: ${now.local_to_utc()}')
println('utc_to_local: ${now.utc_to_local()}')
println('local_unix: ${now.local_unix()}')
println('unix: ${now.unix()}')
println('unix_micro: ${now.unix_micro()}')
println('unix_milli: ${now.unix_milli()}')
println('unix_nano: ${now.unix_nano()}')
println('utc_string: ${now.utc_string()}')
// ==========================================
// Relative and serialization helpers
// ==========================================
// relative() and relative_short() return values like "2 hours ago".
// to_json() returns the JSON representation of the time.
// push_to_http_header() format HTTP-standard cookie/caching header dates.
println('relative: ${now.relative()}')
println('relative_short: ${now.relative_short()}')
println('debug: ${now.debug()}')
println('str: ${now.str()}')
println('to_json: ${now.to_json()}')
mut header_buffer := []u8{}
now.push_to_http_header(mut header_buffer)
println('http_header_string: ${now.http_header_string()}')
println('push_to_http_header: ${header_buffer.bytestr()}')
// ==========================================
// JSON parsing helpers
// ==========================================
// Parse Unix timestamps or ISO/RFC 3339 strings directly back into a Time struct.
mut parsed_from_number := time.now()
parsed_from_number.from_json_number('1712345678') or {
println('from_json_number error: ${err}')
}
println('from_json_number: ${parsed_from_number}')
mut parsed_from_string := time.now()
parsed_from_string.from_json_string('2024-04-06T12:34:56Z') or {
println('from_json_string error: ${err}')
}
println('from_json_string: ${parsed_from_string}')
// ==========================================
// Stopwatch example
// ==========================================
// new_stopwatch starts a new stopwatch to measure high-precision elapsed code execution time.
println('Starting stopwatch...')
mut sw := time.new_stopwatch()
time.sleep(150 * time.millisecond)
println('Elapsed: ${sw.elapsed().milliseconds()} ms')
}
Http Client
Http Client
V has a very rich and growing standard library and is actively updated. This lesson on Http Client showcases modern standard library packages, system calls, network sockets, inline assembly, or WASM support.
Additional Context from Repository docs:
This example demonstrates the concepts of http client.
module main
import net.http
fn main() {
// 1. HTTP GET Request
println('Sending GET request to vlang.io...')
get_resp := http.get('https://vlang.io') or {
println('GET request failed: ${err}')
return
}
println('GET Status Code: ${get_resp.status_code}')
// Reading a response header
content_type := get_resp.header.get(.content_type) or { 'unknown' }
println('GET Content-Type Header: ${content_type}')
println('GET Body length: ${get_resp.body.len} bytes\n')
// 2. HTTP POST Request
println('Sending POST request to httpbin.org...')
post_body := 'Hello V Standard Library!'
post_resp := http.post('https://httpbin.org/post', post_body) or {
println('POST request failed: ${err}')
return
}
println('POST Status Code: ${post_resp.status_code}')
println('POST Response Body:')
println(post_resp.body)
}
Regex Matching
Regex Matching
V has a very rich and growing standard library and is actively updated. This lesson on Regex Matching showcases modern standard library packages, system calls, network sockets, inline assembly, or WASM support.
Additional Context from Repository docs:
This example demonstrates the concepts of regex matching.
module main
import regex
// replace_callback is used by replace_by_fn() to show how a match can be rewritten.
fn replace_callback(re regex.RE, in_txt string, start int, end int) string {
return '[${start}-${end}]'
}
fn main() {
// This sample text contains numbers and words that the regex API will inspect.
text := 'We have 15 apples, 32 bananas, and 120 oranges.'
// Compile a regex that finds one or more digits.
mut re := regex.regex_opt(r'\d+') or {
println('Failed to compile regex: ${err}')
return
}
// Create another regex object and compile a word-matching pattern.
mut re_from_new := regex.new()
re_from_new.compile_opt(r'\w+') or {
println('compile_opt() failed: ${err}')
return
}
// regex_base() returns the compiled regex plus a status code and error message.
base_re, base_code, base_err := regex.regex_base(r'\d+')
println('regex_base(): ${base_code}, ${base_err}')
println('regex_base query: ${base_re.get_query()}')
println('=== regex module demo ===')
println('query: ${re.get_query()}')
// find() returns the first match position and span.
start, end := re.find(text)
if start >= 0 {
matched := text[start..end]
println('find(): "${matched}" at (${start}, ${end})')
} else {
println('find(): no match')
}
// The next calls demonstrate the other common regex helpers.
println('find_from(): ${re.find_from(text, 10)}')
println('find_all(): ${re.find_all(text)}')
println('find_all_str(): ${re.find_all_str(text)}')
println('match_string(): ${re.match_string(text)}')
println('matches_string(): ${re.matches_string(text)}')
println('replace(): ${re.replace(text, 'NUM')}')
println('replace_n(): ${re.replace_n(text, 'NUM', 2)}')
println('replace_simple(): ${re.replace_simple(text, 'NUM')}')
println('replace_by_fn(): ${re.replace_by_fn(text, replace_callback)}')
println('split(): ${re.split(text)}')
println('get_group_list(): ${re.get_group_list()}')
println('get_code(): ${re.get_code()}')
println('get_group_by_id(): ${re.get_group_by_id(text, 0)}')
println('get_group_by_name(): ${re.get_group_by_name(text, '')}')
println('get_group_bounds_by_id(): ${re.get_group_bounds_by_id(0)}')
println('get_group_bounds_by_name(): ${re.get_group_bounds_by_name('')}')
println('match_base(): ${unsafe { re.match_base(text.str, text.len) }}')
// reset() clears the regex state so we can reuse the object.
re.reset()
println('reset() query: ${re.get_query()}')
println('new() compile_opt query: ${re_from_new.get_query()}')
}
Command Line Flags
Command Line Flags
V has a very rich and growing standard library and is actively updated. This lesson on Command Line Flags showcases modern standard library packages, system calls, network sockets, inline assembly, or WASM support.
Additional Context from Repository docs:
This example demonstrates the concepts of command line flags.
module main
import flag
import os
fn main() {
// 1. Initialize the flag parser with command line arguments (os.args)
mut fp := flag.new_flag_parser(os.args)
fp.application('greet-tool')
fp.version('1.0.0')
fp.description("A simple CLI greeting utility demonstrating V's flag module.")
// 2. Skip the executable name during parsing
fp.skip_executable()
// 3. Define flags with their types, short abbreviations, default values, and descriptions
// The second argument is a u8 rune for the short flag (e.g. `n` for -n), or `0` for none.
name := fp.string('name', `n`, 'Guest', 'The name of the person to greet')
verbose := fp.bool('verbose', `v`, false, 'Enable verbose logging output')
count := fp.int('count', `c`, 1, 'Number of times to print the greeting')
// 4. Finalize parsing. This returns remaining non-flag arguments or an error.
additional_args := fp.finalize() or {
println('Error: ${err}')
println(fp.usage())
return
}
if verbose {
println('Verbose Mode: ON')
println('Parsing completed successfully.')
}
// 5. Use the parsed variables
for i in 0 .. count {
println('Hello, ${name}! (greeting ${i + 1}/${count})')
}
if additional_args.len > 0 {
println('Additional non-flag arguments: ${additional_args}')
}
}
Datatypes Collections
Datatypes Collections
V's standard library provides a rich set of built-in collections and data structures through the datatypes module. Here is the simplest, most practical guide to when you should actually use each of these data structures in real-world programming.
1. Bloom Filter
- The Vibe: The "Fast bouncer at the door."
- What it does: It tells you with 100% certainty if something is not there, but if it says something is there, it might be guessing (a false positive).
- Best to use when: You have a massive database and searching it takes too long. You use a Bloom Filter as a quick shield. If the filter says "Nope, that username doesn't exist," you don't waste time searching the database.
- Real-world example: Checking if a chosen username is taken, or filtering out malicious URLs before loading a website.
2. Set
- The Vibe: The "No Duplicates Allowed" club.
- What it does: Stores a collection of items where everything must be unique. It also lets you do math operations like combining two groups (Union) or finding what they have in common (Intersection).
- Best to use when: You need to filter out duplicates instantly, or you need to compare two groups of data to find common ground.
- Real-world example: Storing unique visitor IP addresses on a website, or finding a list of mutual friends between you and someone else.
3. Queue
- The Vibe: Waiting in line at a grocery store (First In, First Out / FIFO).
- What it does: The first item you put in is the first item you take out.
- Best to use when: You have tasks that need to be processed exactly in the order they arrived.
- Real-world example: A printer queue handling documents, or customer support tickets waiting to be answered by an agent.
4. Stack
- The Vibe: A stack of dinner plates (Last In, First Out / LIFO).
- What it does: The last item you put on top is the first one you have to take off.
- Best to use when: You need to keep track of a history of actions so you can reverse them, or track active processes.
- Real-world example: The "Undo" ($Ctrl+Z$) feature in a text editor, or the "Back" button history in your web browser.
5. Ring Buffer (Circular Buffer)
- The Vibe: A streaming video that continuously overwrites itself.
- What it does: A queue with a strict maximum size. When it gets full, new data wraps around to the beginning and overwrites the oldest data.
- Best to use when: You are handling a continuous stream of data and you only care about the most recent information, without wasting memory.
- Real-world example: Audio/video streaming playback buffers, or a flight data recorder ("black box") that only saves the last 24 hours of flight data.
6. Min Heap
- The Vibe: A VIP line where the most urgent person always gets to go first.
- What it does: A specialized structure that always keeps the smallest (or highest priority) value at the very top.
- Best to use when: You need to constantly pull the lowest/highest value out of a changing list without sorting the entire list every single time.
- Real-world example: A hospital emergency room triage system, or GPS apps calculating the shortest route dynamically.
7. BSTree (Binary Search Tree)
- The Vibe: A perfectly organized filing cabinet.
- What it does: Keeps data sorted automatically as you add it. Smaller numbers go left, larger numbers go right.
- Best to use when: You need to search, add, and delete items constantly, and you always need the data to stay in perfect alphabetical or numerical order.
- Real-world example: File systems on your computer, or database indexing to make searching millions of records instant.
8. LinkedList vs. DoublyLinkedList
- The Vibe: A scavenger hunt. Item A gives you a clue to find Item B, which gives you a clue to find Item C.
- What it does:
- LinkedList (Singly): Each item points only to the next item. You can only move forward.
- DoublyLinkedList: Each item points to both the next item and the previous item. You can move forward and backward.
- Best to use when: You are constantly adding or removing items from the very beginning or middle of a list. (Standard arrays are slow at this because they have to shift all the other items over; Linked Lists just change where the "clues" point).
- Real-world example: A music playlist. A regular linked list only lets you hit "Next". A doubly linked list lets you hit "Next" and "Previous".
Additional Context from Repository docs:
This example demonstrates the concepts of datatypes collections.
module main
import datatypes
// This helper creates a stable hash value for the bloom filter demo.
fn hash_string(value string) u32 {
mut hash := u32(2166136261)
for ch in value {
hash ^= u32(ch)
hash *= 16777619
}
return hash
}
fn main() {
println('=== datatypes collection demo ===')
// BloomFilter is a probabilistic structure used to test membership quickly.
println('\n--- BloomFilter ---')
mut bloom := datatypes.new_bloom_filter[string](hash_string, 64, 3) or { panic(err) }
bloom.add('apple')
bloom.add('banana')
bloom_exists_apple := bloom.exists('apple')
println('bloom exists apple: ${bloom_exists_apple}')
bloom_exists_cherry := bloom.exists('cherry')
println('bloom exists cherry: ${bloom_exists_cherry}')
mut bloom_fast := datatypes.new_bloom_filter[string](hash_string, 64, 3) or { panic(err) }
bloom_fast.add('date')
fast_exists_date := bloom_fast.exists('date')
println('fast bloom exists date: ${fast_exists_date}')
// The union and intersection methods combine two bloom filters.
union_bloom := bloom.@union(bloom_fast) or { panic(err) }
union_exists_banana := union_bloom.exists('banana')
println('union bloom exists banana: ${union_exists_banana}')
intersection_bloom := bloom.intersection(bloom_fast) or { panic(err) }
intersection_exists_apple := intersection_bloom.exists('apple')
println('intersection bloom exists apple: ${intersection_exists_apple}')
// BSTree stores values in sorted order and supports tree traversal.
println('\n--- BSTree ---')
mut bst := datatypes.BSTree[int]{}
bst_is_empty := bst.is_empty()
println('empty before inserts: ${bst_is_empty}')
bst.insert(10)
bst.insert(5)
bst.insert(15)
bst.insert(12)
bst_contains_12 := bst.contains(12)
println('contains 12: ${bst_contains_12}')
in_order := bst.in_order_traversal()
println('in_order: ${in_order}')
pre_order := bst.pre_order_traversal()
println('pre_order: ${pre_order}')
post_order := bst.post_order_traversal()
println('post_order: ${post_order}')
left_val := bst.to_left(10) or { -1 }
println('left of 10: ${left_val}')
right_val := bst.to_right(10) or { -1 }
println('right of 10: ${right_val}')
min_val := bst.min() or { -1 }
println('min: ${min_val}')
max_val := bst.max() or { -1 }
println('max: ${max_val}')
bst.remove(5)
bst_contains_5_after_remove := bst.contains(5)
println('contains 5 after remove: ${bst_contains_5_after_remove}')
// DoublyLinkedList supports inserting and iterating from both ends.
println('\n--- DoublyLinkedList ---')
mut dll := datatypes.DoublyLinkedList[string]{}
dll.push_back('one')
dll.push_front('zero')
dll.push_many(['two', 'three'], datatypes.Direction.back)
dll_array := dll.array()
println('dll array: ${dll_array}')
dll_first := dll.first() or { 'none' }
println('dll first: ${dll_first}')
dll_last := dll.last() or { 'none' }
println('dll last: ${dll_last}')
dll_index_two := dll.index('two') or { -1 }
println('dll index of two: ${dll_index_two}')
dll.insert(2, 'inserted') or { panic(err) }
dll_after_insert := dll.array()
println('dll after insert: ${dll_after_insert}')
dll.delete(1)
dll_after_delete := dll.array()
println('dll after delete: ${dll_after_delete}')
dll_str := dll.str()
println('dll str: ${dll_str}')
dll_next := dll.next() or { 'none' }
println('dll next: ${dll_next}')
mut dll_iter := dll.iterator()
for {
if value := dll_iter.next() {
println('dll iter: ${value}')
} else {
break
}
}
mut dll_back_iter := dll.back_iterator()
for {
if value := dll_back_iter.next() {
println('dll back iter: ${value}')
} else {
break
}
}
dll_pop_front := dll.pop_front() or { 'none' }
println('dll pop_front: ${dll_pop_front}')
dll_pop_back := dll.pop_back() or { 'none' }
println('dll pop_back: ${dll_pop_back}')
dll_final := dll.array()
println('dll final: ${dll_final}')
// LinkedList shows a simple singly linked sequence with push/pop helpers.
println('\n--- LinkedList ---')
mut linked_list := datatypes.LinkedList[int]{}
linked_list_is_empty := linked_list.is_empty()
println('linked list empty: ${linked_list_is_empty}')
linked_list.push(1)
linked_list.push(2)
linked_list.push_many([3, 4])
linked_list.prepend(0)
linked_list.insert(2, 5) or { panic(err) }
linked_list_array := linked_list.array()
println('linked list array: ${linked_list_array}')
linked_list_first := linked_list.first() or { -1 }
println('linked list first: ${linked_list_first}')
linked_list_last := linked_list.last() or { -1 }
println('linked list last: ${linked_list_last}')
linked_list_index_3 := linked_list.index(3) or { -1 }
println('linked list index 3: ${linked_list_index_3}')
linked_list_str := linked_list.str()
println('linked list str: ${linked_list_str}')
linked_list_pop := linked_list.pop() or { -1 }
println('linked list pop: ${linked_list_pop}')
linked_list_shift := linked_list.shift() or { -1 }
println('linked list shift: ${linked_list_shift}')
linked_list_next := linked_list.next() or { -1 }
println('linked list next: ${linked_list_next}')
mut list_iter := linked_list.iterator()
for {
if value := list_iter.next() {
println('linked list iter: ${value}')
} else {
break
}
}
linked_list_len := linked_list.len()
println('linked list len: ${linked_list_len}')
// MinHeap keeps the smallest value at the front.
println('\n--- MinHeap ---')
mut heap := datatypes.MinHeap[int]{}
heap.insert(8)
heap.insert(3)
heap.insert_many([5, 1, 7])
heap_len := heap.len()
println('heap len: ${heap_len}')
heap_peek := heap.peek() or { -1 }
println('heap peek: ${heap_peek}')
heap_pop_1 := heap.pop() or { -1 }
println('heap pop: ${heap_pop_1}')
heap_pop_2 := heap.pop() or { -1 }
println('heap pop: ${heap_pop_2}')
// Queue shows FIFO behavior and the standard enqueue/dequeue helpers.
println('\n--- Queue ---')
mut queue := datatypes.Queue[int]{}
queue_is_empty := queue.is_empty()
println('queue empty: ${queue_is_empty}')
queue.push(100)
queue.push(200)
queue.push(300)
queue_len := queue.len()
println('queue len: ${queue_len}')
queue_array := queue.array()
println('queue array: ${queue_array}')
queue_peek := queue.peek() or { -1 }
println('queue peek: ${queue_peek}')
queue_last := queue.last() or { -1 }
println('queue last: ${queue_last}')
queue_index_2 := queue.index(2) or { -1 }
println('queue index 2: ${queue_index_2}')
queue_str := queue.str()
println('queue str: ${queue_str}')
queue_pop_1 := queue.pop() or { -1 }
println('queue pop: ${queue_pop_1}')
queue_pop_2 := queue.pop() or { -1 }
println('queue pop: ${queue_pop_2}')
// RingBuffer provides bounded storage with wraparound behavior.
println('\n--- RingBuffer ---')
mut rb := datatypes.new_ringbuffer[string](4)
rb_is_empty := rb.is_empty()
println('rb empty: ${rb_is_empty}')
rb.push('first') or { panic(err) }
rb.push('second') or { panic(err) }
rb.push('third') or { panic(err) }
rb_occupied := rb.occupied()
println('rb occupied: ${rb_occupied}')
rb_remaining := rb.remaining()
println('rb remaining: ${rb_remaining}')
rb_pop := rb.pop() or { 'empty' }
println('rb pop: ${rb_pop}')
rb_pop_many := rb.pop_many(2) or { []string{} }
println('rb pop_many: ${rb_pop_many}')
rb_is_full := rb.is_full()
println('rb full: ${rb_is_full}')
rb.clear()
rb_after_clear := rb.is_empty()
println('rb after clear: ${rb_after_clear}')
// Set demonstrates unique values and set algebra operations.
println('\n--- Set ---')
mut set_a := datatypes.Set[string]{}
set_a.add_all(['apple', 'banana', 'cherry', 'apple'])
set_a_array := set_a.array()
println('set_a: ${set_a_array}')
set_a_size := set_a.size()
println('set_a size: ${set_a_size}')
set_a_contains_banana := set_a.exists('banana')
println('contains banana: ${set_a_contains_banana}')
set_a.remove('banana')
set_a_after_remove := set_a.array()
println('after remove: ${set_a_after_remove}')
set_a_pick := set_a.pick() or { 'empty' }
println('pick: ${set_a_pick}')
set_a_rest := set_a.rest() or { []string{} }
println('rest: ${set_a_rest}')
set_a_pop := set_a.pop() or { 'empty' }
println('pop: ${set_a_pop}')
set_a_is_empty := set_a.is_empty()
println('is_empty: ${set_a_is_empty}')
set_a.clear()
set_a_after_clear := set_a.is_empty()
println('cleared: ${set_a_after_clear}')
mut set_b := datatypes.Set[string]{}
set_b.add_all(['apple', 'cherry'])
mut set_c := datatypes.Set[string]{}
set_c.add_all(['cherry', 'date'])
union_set := set_b.@union(set_c).array()
println('union: ${union_set}')
intersection_set := set_b.intersection(set_c).array()
println('intersection: ${intersection_set}')
// Compute the difference manually to avoid V compiler/analyzer issues with generic operator overloading (-)
mut diff_set := set_b.copy()
for item in set_c.array() {
diff_set.remove(item)
}
diff_array := diff_set.array()
println('difference: ${diff_array}')
is_subset := set_b.subset(set_c)
println('subset: ${is_subset}')
copied_set := set_b.copy().array()
println('copy: ${copied_set}')
// Stack demonstrates LIFO behavior with push/pop operations.
println('\n--- Stack ---')
mut stack := datatypes.Stack[string]{}
stack_is_empty := stack.is_empty()
println('stack empty: ${stack_is_empty}')
stack.push('first')
stack.push('second')
stack.push('third')
stack_len := stack.len()
println('stack len: ${stack_len}')
stack_array := stack.array()
println('stack contents: ${stack_array}')
stack_peek := stack.peek() or { 'empty' }
println('stack peek: ${stack_peek}')
stack_str := stack.str()
println('stack str: ${stack_str}')
stack_pop_1 := stack.pop() or { 'empty' }
println('stack pop: ${stack_pop_1}')
stack_pop_2 := stack.pop() or { 'empty' }
println('stack pop: ${stack_pop_2}')
}
Gg Graphics
Gg Graphics
V has a very rich and growing standard library and is actively updated. This lesson on Gg Graphics showcases modern standard library packages, system calls, network sockets, inline assembly, or WASM support.
Additional Context from Repository docs:
This example demonstrates the concepts of gg graphics using V's simple graphics module. It shows how to initialize a window, define an application state struct, draw various 2D shapes (rectangles, circles, triangles, polygons, lines), render formatted text, and intercept keyboard and mouse event inputs.
module main
import gg
import math
// AppContext holds the state of our graphical application.
// Using a state struct is a recommended best practice for gg applications
// to avoid global variables (which V does not support by default).
struct AppContext {
mut:
ctx &gg.Context = unsafe { nil }
width int = 800
height int = 600
// Interactive shape parameters
shape_x f32 = 400.0
shape_y f32 = 300.0
shape_size f32 = 50.0
shape_color gg.Color = gg.blue
active_shape int // 0 = Circle, 1 = Rectangle, 2 = Triangle
// Tracking mouse positions and clicks
mouse_x f32
mouse_y f32
click_x f32 = -1.0
click_y f32 = -1.0
click_color gg.Color = gg.red
// Last key pressed message
last_key string = 'None'
}
fn main() {
// Initialize state
mut app := &AppContext{
width: 800
height: 600
}
// Create a new gg context.
// You specify callbacks for rendering frames, processing events,
// and cleanups, along with initial window configurations.
app.ctx = gg.new_context(
width: app.width
height: app.height
window_title: "V's gg graphics module: Tutorial & Interactive Demo"
bg_color: gg.rgb(240, 244, 248) // A subtle modern light-blue background
user_data: app // Pass our state struct to be accessible inside callbacks
frame_fn: frame // Callback called once per frame (to draw shapes/UI)
event_fn: on_event // Callback called for user inputs (mouse & keyboard)
)
println('Starting interactive graphics window...')
println('Controls:')
println(' - Move the mouse to see cursor position tracking.')
println(' - Left-Click anywhere to draw a red dot at the click location.')
println(' - Use Arrow Keys (Up/Down/Left/Right) to move the active shape.')
println(" - Press [C] or [c] to cycle the active shape's color.")
println(' - Press [S] or [s] to toggle between Circle, Rectangle, and Triangle.')
println(' - Press [Escape] to close the window.')
println('\nReal-world Case Study:')
println(' - Check out a complete journaling application built with gg:')
println(' https://github.com/codecaine-zz/MindSpace-Journal')
// Start the application main event loop.
app.ctx.run()
}
// frame is the drawing function called per frame.
// All rendering code MUST reside between ctx.begin() and ctx.end().
fn frame(data voidptr) {
mut app := unsafe { &AppContext(data) }
mut ctx := app.ctx
ctx.begin()
// --- 1. Draw Static 2D Shapes ---
// Draw a thick horizontal line dividing the header from the demo workspace
ctx.draw_line(0, 80, app.width, 80, gg.gray)
// Draw a filled rectangle (Left)
ctx.draw_rect_filled(50, 120, 120, 80, gg.green)
// Draw an empty/outline rectangle just next to it
ctx.draw_rect_empty(200, 120, 120, 80, gg.dark_gray)
// Draw a filled circle (Middle-Left)
ctx.draw_circle_filled(430, 160, 45, gg.orange)
// Draw an empty/outline circle
ctx.draw_circle_empty(560, 160, 45, gg.purple)
// Draw a filled triangle (Middle-Right)
ctx.draw_triangle_filled(700, 115, 750, 205, 650, 205, gg.pink)
// Draw a custom convex polygon (a star-like pentagon, bottom right)
poly_points := [
f32(650.0),
480.0, // Point 1
750.0,
480.0, // Point 2
780.0,
560.0, // Point 3
700.0,
520.0, // Point 4
620.0,
560.0, // Point 5
]
ctx.draw_convex_poly(poly_points, gg.cyan)
// --- 2. Render Text using custom configurations (TextCfg) ---
// Main Title with default configuration
ctx.draw_text_def(20, 15, "Welcome to V's Simple Graphics (gg) Tutorial!")
// Instructions and state metadata at the top right
ctx.draw_text(20, 45, 'Cursor: (${app.mouse_x:.1f}, ${app.mouse_y:.1f}) | Last Key: ${app.last_key}',
color: gg.dark_blue
size: 16
bold: true
)
// Context description
ctx.draw_text(50, 215, 'Filled & Empty Rectangles', size: 12, color: gg.dark_gray)
ctx.draw_text(400, 215, 'Filled & Empty Circles', size: 12, color: gg.dark_gray)
ctx.draw_text(650, 215, 'Filled Triangle', size: 12, color: gg.dark_gray)
ctx.draw_text(630, 570, 'Convex Polygon (Pentagon)', size: 12, color: gg.dark_gray)
// Help panel explaining key bindings
ctx.draw_rect_filled(20, 440, 280, 140, gg.Color{ r: 255, g: 255, b: 255, a: 180 })
ctx.draw_rect_empty(20, 440, 280, 140, gg.gray)
ctx.draw_text(35, 450, 'Controls Panel', size: 15, bold: true, color: gg.black)
ctx.draw_text(35, 475, '- Arrows: Move active shape', size: 13, color: gg.black)
ctx.draw_text(35, 495, '- C: Cycle shape color', size: 13, color: gg.black)
ctx.draw_text(35, 515, '- S: Switch shape type', size: 13, color: gg.black)
ctx.draw_text(35, 535, '- Mouse Click: Draw a dot', size: 13, color: gg.black)
ctx.draw_text(35, 555, '- Escape: Quit application', size: 13, color: gg.black)
// Case Study reference
ctx.draw_text(20, 585, 'Case Study: github.com/codecaine-zz/MindSpace-Journal',
size: 10
italic: true
color: gg.dark_blue
)
// --- 3. Draw Dynamic / Interactive Elements ---
// Draw a dot where the user clicked, if a click has occurred
if app.click_x >= 0.0 {
ctx.draw_circle_filled(app.click_x, app.click_y, 8, app.click_color)
ctx.draw_circle_empty(app.click_x, app.click_y, 12, gg.black)
ctx.draw_text(int(app.click_x) + 12, int(app.click_y) - 6, 'Last Click: (${app.click_x:.0f}, ${app.click_y:.0f})',
size: 11
color: gg.black
)
}
// Draw the active shape controlled by the user
match app.active_shape {
0 {
// Draw interactive Circle
ctx.draw_circle_filled(app.shape_x, app.shape_y, app.shape_size, app.shape_color)
ctx.draw_circle_empty(app.shape_x, app.shape_y, app.shape_size, gg.black)
}
1 {
// Draw interactive Rectangle (centered on coordinates)
half := app.shape_size
ctx.draw_rect_filled(app.shape_x - half, app.shape_y - half, app.shape_size * 2,
app.shape_size * 2, app.shape_color)
ctx.draw_rect_empty(app.shape_x - half, app.shape_y - half, app.shape_size * 2,
app.shape_size * 2, gg.black)
}
2 {
// Draw interactive Equilateral Triangle (centered on coordinates)
// Using trigonometry to draw an equilateral triangle of size `shape_size`
h := app.shape_size * f32(math.sqrt(3.0)) / 2.0
x1 := app.shape_x
y1 := app.shape_y - (2.0 / 3.0) * h
x2 := app.shape_x - app.shape_size
y2 := app.shape_y + (1.0 / 3.0) * h
x3 := app.shape_x + app.shape_size
y3 := app.shape_y + (1.0 / 3.0) * h
ctx.draw_triangle_filled(x1, y1, x2, y2, x3, y3, app.shape_color)
}
else {}
}
// Draw label above the active shape
ctx.draw_text(int(app.shape_x) - 40, int(app.shape_y) - int(app.shape_size) - 20,
'Active Shape',
size: 13
bold: true
color: gg.black
)
ctx.end()
}
// on_event intercepts and handles all system-level user inputs.
fn on_event(e &gg.Event, data voidptr) {
mut app := unsafe { &AppContext(data) }
mut ctx := app.ctx
match e.typ {
.mouse_move {
app.mouse_x = e.mouse_x
app.mouse_y = e.mouse_y
}
.mouse_down {
app.click_x = e.mouse_x
app.click_y = e.mouse_y
// Randomize click dot color slightly for visual variety
if app.click_color.r == 255 {
app.click_color = gg.Color{
r: 0
g: 180
b: 0
a: 255
}
} else {
app.click_color = gg.red
}
}
.key_down {
app.last_key = e.key_code.str()
match e.key_code {
.escape {
ctx.quit()
}
// Change Color when 'C' or 'c' is pressed
.c {
if app.shape_color.r == 0 && app.shape_color.b == 255 { // blue -> red
app.shape_color = gg.red
} else if app.shape_color.r == 255 && app.shape_color.g == 0 { // red -> green
app.shape_color = gg.green
} else { // green -> blue
app.shape_color = gg.blue
}
}
// Toggle shape type when 'S' or 's' is pressed
.s {
app.active_shape = (app.active_shape + 1) % 3
}
// Use Arrow Keys to move the active shape
.left {
app.shape_x -= 15.0
if app.shape_x < 0 {
app.shape_x = 0
}
}
.right {
app.shape_x += 15.0
if app.shape_x > app.width {
app.shape_x = f32(app.width)
}
}
.up {
app.shape_y -= 15.0
if app.shape_y < 80 {
app.shape_y = 80
}
// Keep below divider line
}
.down {
app.shape_y += 15.0
if app.shape_y > app.height {
app.shape_y = f32(app.height)
}
}
else {}
}
}
else {}
}
}
Command Line Arguments
This example demonstrates how to directly access and parse command-line arguments using os.args to build simple command-line applications.
module main
import os
fn main() {
// os.args is a []string containing all command line arguments.
// os.args[0] is always the name of the executable (or the script path if run via v run).
// os.args[1..] contains the actual command-line arguments passed to the program.
println('Executable / script path: ${os.args[0]}')
println('Total arguments count: ${os.args.len}')
println('All arguments list: ${os.args}')
if os.args.len < 2 {
println('\nUsage: v run command_line_arguments.v <command> [arguments...]')
println('Try running: v run command_line_arguments.v greet Alice')
println('Try running: v run command_line_arguments.v sum 3 5 8')
return
}
command := os.args[1]
args := os.args[2..]
println('\nProcessing command: "${command}" with args: ${args}')
match command {
'greet' {
if args.len < 1 {
println('Error: greet command requires a name.')
return
}
name := args[0]
println('Hello, ${name}!')
}
'sum' {
if args.len < 1 {
println('Error: sum command requires at least one number.')
return
}
mut total := 0
for arg in args {
num := arg.int()
total += num
}
println('Sum of numbers: ${total}')
}
else {
println('Unknown command: "${command}". Allowed commands: "greet", "sum".')
}
}
}
Math And Rand
Math And Rand
V's math and rand modules expose far more than the basic trigonometry and random-int helpers. The example below expands the repository walkthrough with additional documented functions from the current V docs, including exponentiation, clamping, logarithms, bytes, hex strings, ULIDs, and ranged integer generation.
module main
import math
import rand
fn main() {
println('=== Math & Rand Module Examples ===')
println('\n--- math ---')
println('Pi constant: ${math.pi}')
println('E constant: ${math.e}')
angle := 45.0 * (math.pi / 180.0)
println('sin(45 deg): ${math.sin(angle):.4f}')
println('cos(45 deg): ${math.cos(angle):.4f}')
println('tan(45 deg): ${math.tan(angle):.4f}')
println('2^10: ${math.pow(2.0, 10.0)}')
println('sqrt(144): ${math.sqrt(144.0)}')
println('ln(e): ${math.log(math.e)}')
println('log10(100): ${math.log10(100.0)}')
println('abs(-5.5): ${math.abs(-5.5)}')
println('max(10, 20): ${math.max(10.0, 20.0)}')
println('min(10, 20): ${math.min(10.0, 20.0)}')
println('ceil(4.2): ${math.ceil(4.2)}')
println('floor(4.8): ${math.floor(4.8)}')
println('round(4.5): ${math.round(4.5)}')
println('cbrt(27): ${math.cbrt(27.0):.2f}')
println('clamp(12, 0, 10): ${math.clamp(12.0, 0.0, 10.0)}')
println('exp(1): ${math.exp(1.0):.4f}')
println('exp2(3): ${math.exp2(3.0):.4f}')
println('hypot(3, 4): ${math.hypot(3.0, 4.0):.4f}')
println('log2(8): ${math.log2(8.0):.4f}')
println('trunc(4.9): ${math.trunc(4.9)}')
println('\n--- rand ---')
random_int := rand.int_in_range(1, 100) or { 0 }
println('Random integer in [1, 100): ${random_int}')
random_f64 := rand.f64()
println('Random f64 in [0.0, 1.0): ${random_f64:.4f}')
random_bool := (rand.intn(2) or { 0 }) == 0
println('Random boolean: ${random_bool}')
items := ['Apple', 'Banana', 'Cherry', 'Date']
chosen := rand.element(items) or { 'None' }
println('Randomly chosen fruit: ${chosen}')
random_bytes := rand.bytes(4) or { []u8{} }
println('Random bytes: ${random_bytes}')
random_hex := rand.hex(8)
println('Random hex string: ${random_hex}')
random_string := rand.string(8)
println('Random ascii string: ${random_string}')
random_ulid := rand.ulid()
println('Random ULID: ${random_ulid}')
random_i64 := rand.i64_in_range(i64(-10), i64(10)) or { 0 }
println('Random i64 in [-10, 10]: ${random_i64}')
mut uuid_str := rand.uuid_v4()
println('Random UUID v4: ${uuid_str}')
uuid_str = rand.uuid_v7()
println('Random UUID v7: ${uuid_str}')
}
Crypto Asymmetric
Crypto Asymmetric
V has a very rich and growing standard library and is actively updated. This lesson on Crypto Asymmetric showcases modern standard library packages, system calls, network sockets, inline assembly, or WASM support.
module main
import crypto.ecdsa
import crypto.ed25519
import crypto.pem
fn main() {
println('=== V Asymmetric Cryptography Demo ===')
message := 'Message to sign and verify asymmetric signatures.'.bytes()
// --- 1. ECDSA ---
println('\n--- ECDSA ---')
// Generate key pair
pub_ec, priv_ec := ecdsa.generate_key() or {
println('Failed to generate ECDSA key: ${err}')
return
}
// Sign message
sig_ec := priv_ec.sign(message, ecdsa.SignerOpts{}) or {
println('ECDSA signing failed: ${err}')
return
}
println('ECDSA Signature (Hex): ${sig_ec.hex()}')
// Verify message
verified_ec := pub_ec.verify(message, sig_ec, ecdsa.SignerOpts{}) or {
println('ECDSA verification error: ${err}')
return
}
println('ECDSA Signature Verified? -> ${verified_ec}')
// --- 2. Ed25519 ---
println('\n--- Ed25519 ---')
// Generate key pair
pub_ed, priv_ed := ed25519.generate_key() or {
println('Failed to generate Ed25519 key: ${err}')
return
}
// Sign message
sig_ed := ed25519.sign(priv_ed, message) or {
println('Ed25519 signing failed: ${err}')
return
}
println('Ed25519 Signature (Hex): ${sig_ed.hex()}')
// Verify message
verified_ed := ed25519.verify(pub_ed, message, sig_ed) or {
println('Ed25519 verification error: ${err}')
return
}
println('Ed25519 Signature Verified? -> ${verified_ed}')
// --- 3. PEM Encoding/Decoding ---
println('\n--- PEM (Privacy Enhanced Mail) Encoding ---')
pub_bytes := pub_ec.bytes() or {
println('Failed to get public key bytes: ${err}')
return
}
mut pem_block := pem.Block.new('EC PUBLIC KEY')
pem_block.data = pub_bytes
pem_string := pem_block.encode(pem.EncodeConfig{}) or {
println('PEM encoding failed: ${err}')
return
}
println('Encoded PEM Public Key:')
println(pem_string)
// Decode back
decoded_block, _ := pem.decode(pem_string) or {
println('PEM decoding failed')
return
}
println('Decoded Block Type: "${decoded_block.block_type}"')
println('Decoded data size matches? -> ${decoded_block.data.len == pub_bytes.len}')
}
Crypto Entropy
Crypto Entropy
V has a very rich and growing standard library and is actively updated. This lesson on Crypto Entropy showcases modern standard library packages, system calls, network sockets, inline assembly, or WASM support.
module main
import crypto.rand
import math.big
fn main() {
println('=== V Secure Randomness (Entropy) Demo ===')
// --- 1. Generating Secure Random Bytes ---
println('\n--- Secure Random Bytes ---')
// Generates securely generated random bytes from the OS entropy pool
random_bytes := rand.bytes(16) or {
println('Failed to generate secure bytes: ${err}')
return
}
println('Generated 16 secure bytes (Hex): ${random_bytes.hex()}')
// --- 2. Generating Secure Random u64 ---
println('\n--- Secure Random u64 ---')
// Generates a random u64 in the range [0, max)
limit_u64 := u64(10_000)
random_val := rand.int_u64(limit_u64) or {
println('Failed to generate random u64: ${err}')
return
}
println('Secure random u64 in [0, ${limit_u64}): ${random_val}')
// --- 3. Generating Secure Random Big Integer ---
println('\n--- Secure Random big.Integer ---')
// Generates a random big.Integer in the range [0, limit)
limit_str := '10000000000000000000000000000000000000000' // 10^40
limit_big := big.integer_from_string(limit_str) or {
println('Failed to parse big integer string: ${err}')
return
}
random_big := rand.int_big(limit_big) or {
println('Failed to generate random big integer: ${err}')
return
}
println('Secure random big.Integer in [0, 10^40):')
println(random_big.str())
}
Crypto Hash
For a detailed demonstration of every cryptographic module, the standard library examples are structured into subdirectories under language_updates_and_stdlib/02_standard_library/11_log_and_crypto/crypto/.
1. Cryptographic Hash Functions
Demonstrates MD5, SHA-1, SHA-256, SHA-512, SHA-3 (Keccak-256/Keccak-512), RIPEMD-160, BLAKE2b, BLAKE2s, and BLAKE3.
2. Symmetric Ciphers & Block Modes
Demonstrates AES (CBC block mode with PKCS7-like padding), DES, Blowfish (encryption-only), RC4 stream cipher, and general block modes.
3. Asymmetric Cryptography & PEM Formats
Demonstrates ECDSA key generation, signing, and verification; Ed25519 signing and verification; and PEM block encoding/decoding.
4. Key Derivation Functions (KDF)
Demonstrates secure password hashing and key derivation using Bcrypt, Scrypt, and PBKDF2.
5. Message Authentication Codes (MAC)
Demonstrates message integrity and authenticity verification using HMAC-SHA256.
6. Secure Randomness & Entropy
Demonstrates generating secure cryptographically random bytes, u64 values, and large integers (big.Integer).
module main
import crypto.md5
import crypto.sha1
import crypto.sha256
import crypto.sha512
import crypto.sha3
import crypto.ripemd160
import crypto.blake2b
import crypto.blake2s
import crypto.blake3
fn main() {
println('=== V Cryptographic Hash Algorithms ===')
input := 'V Language Crypto Guide'.bytes()
input_str := 'V Language Crypto Guide'
// 1. MD5 (128-bit)
md5_hex := md5.hexhash(input_str)
println('MD5: ${md5_hex}')
// 2. SHA-1 (160-bit)
sha1_hex := sha1.hexhash(input_str)
println('SHA-1: ${sha1_hex}')
// 3. SHA-256 (256-bit)
sha256_hex := sha256.hexhash(input_str)
println('SHA-256: ${sha256_hex}')
// 4. SHA-512 (512-bit)
sha512_hex := sha512.hexhash(input_str)
println('SHA-512: ${sha512_hex}')
// 5. SHA-3 (Keccak-based, 256 and 512 bit sums)
sha3_256 := sha3.sum256(input)
sha3_512 := sha3.sum512(input)
println('SHA3-256: ${sha3_256.hex()}')
println('SHA3-512: ${sha3_512.hex()}')
// 6. RIPEMD-160 (160-bit)
ripemd_hex := ripemd160.hexhash(input_str)
println('RIPEMD160: ${ripemd_hex}')
// 7. BLAKE2b (commonly 512-bit / 256-bit)
blake2b_256 := blake2b.sum256(input)
blake2b_512 := blake2b.sum512(input)
println('BLAKE2b-256: ${blake2b_256.hex()}')
println('BLAKE2b-512: ${blake2b_512.hex()}')
// 8. BLAKE2s (commonly 256-bit)
blake2s_256 := blake2s.sum256(input)
println('BLAKE2s-256: ${blake2s_256.hex()}')
// 9. BLAKE3 (256-bit, highly optimized)
blake3_256 := blake3.sum256(input)
println('BLAKE3-256: ${blake3_256.hex()}')
}
Crypto Kdf
Crypto Kdf
V has a very rich and growing standard library and is actively updated. This lesson on Crypto Kdf showcases modern standard library packages, system calls, network sockets, inline assembly, or WASM support.
module main
import crypto.bcrypt
import crypto.scrypt
import crypto.pbkdf2
import crypto.sha256
fn main() {
println('=== V Key Derivation Functions Demo ===')
// --- 1. Bcrypt ---
println('\n--- Bcrypt ---')
password := 'super_secure_password'.bytes()
hash := bcrypt.generate_from_password(password, bcrypt.default_cost) or {
println('Bcrypt failed: ${err}')
return
}
println('Bcrypt hash: ${hash}')
bcrypt.compare_hash_and_password(password, hash.bytes()) or {
println('Bcrypt verification failed: ${err}')
return
}
println('Bcrypt verification successful!')
// --- 2. Scrypt ---
println('\n--- Scrypt ---')
scrypt_pass := 'my_scrypt_pass'.bytes()
scrypt_salt := 'scrypt_salt'.bytes()
// N=16384, r=8, p=1, key_len=32 (N must be power of 2)
scrypt_key := scrypt.scrypt(scrypt_pass, scrypt_salt, 16384, 8, 1, 32) or {
println('Scrypt failed: ${err}')
return
}
println('Scrypt Key (Hex): ${scrypt_key.hex()}')
// --- 3. PBKDF2 ---
println('\n--- PBKDF2 ---')
pbkdf2_pass := 'my_pbkdf2_pass'.bytes()
pbkdf2_salt := 'pbkdf2_salt'.bytes()
// pbkdf2.key(password, salt, iterations, key_len, hash_fn)
pbkdf2_key := pbkdf2.key(pbkdf2_pass, pbkdf2_salt, 4096, 32, sha256.new()) or {
println('PBKDF2 failed: ${err}')
return
}
println('PBKDF2 Key (Hex): ${pbkdf2_key.hex()}')
}
Crypto Mac
Crypto Mac
V has a very rich and growing standard library and is actively updated. This lesson on Crypto Mac showcases modern standard library packages, system calls, network sockets, inline assembly, or WASM support.
module main
import crypto.hmac
import crypto.sha256
fn main() {
println('=== V Message Authentication Codes (MAC) Demo ===')
// --- 1. HMAC-SHA256 Signature Generation ---
println('\n--- HMAC-SHA256 Signature ---')
key := 'secret_signing_key'.bytes()
message := 'This is a message to be authenticated using HMAC.'.bytes()
// hmac.new(key, data, hash_func, blocksize)
mac := hmac.new(key, message, sha256.sum, sha256.block_size)
println('HMAC (Hex): ${mac.hex()}')
// --- 2. HMAC Verification ---
println('\n--- HMAC Verification ---')
// Re-compute to verify
computed_mac := hmac.new(key, message, sha256.sum, sha256.block_size)
// hmac.equal performs constant-time comparison to prevent timing attacks
is_valid := hmac.equal(mac, computed_mac)
println('Signature matches? -> ${is_valid}')
// Verify with a tampered message
tampered_message := 'This is a message to be authenticated using HMAC!'.bytes()
tampered_mac := hmac.new(key, tampered_message, sha256.sum, sha256.block_size)
is_tampered_valid := hmac.equal(mac, tampered_mac)
println('Tampered signature matches? -> ${is_tampered_valid}')
}
Crypto Symmetric
Crypto Symmetric
V has a very rich and growing standard library and is actively updated. This lesson on Crypto Symmetric showcases modern standard library packages, system calls, network sockets, inline assembly, or WASM support.
module main
import crypto.aes
import crypto.des
import crypto.blowfish
import crypto.rc4
import crypto.cipher
fn main() {
println('=== V Symmetric Cryptography Demo ===')
// --- 1. AES with CBC Block Mode ---
println('\n--- AES (CBC Mode) ---')
aes_key := [u8(1), 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] // 16-byte key (AES-128)
aes_iv := [u8(9), 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4] // 16-byte IV
aes_block := aes.new_cipher(aes_key)
mut aes_enc := cipher.new_cbc(aes_block, aes_iv)
// In CBC mode, data must be padded to block size (16 bytes for AES)
plaintext := 'Hello, V Cryptography! Padding here.'.bytes() // 36 bytes. We need to pad it to 48 bytes (multiple of 16)
mut padded := plaintext.clone()
pad_len := 16 - (padded.len % 16)
for _ in 0 .. pad_len {
padded << u8(pad_len)
}
mut ciphertext := []u8{len: padded.len}
aes_enc.encrypt_blocks(mut ciphertext, padded)
println('Ciphertext (Hex): ${ciphertext.hex()}')
// Decrypt
mut aes_dec := cipher.new_cbc(aes_block, aes_iv)
mut decrypted := []u8{len: ciphertext.len}
aes_dec.decrypt_blocks(mut decrypted, ciphertext)
// Unpad
unpadded_len := decrypted.len - int(decrypted.last())
unpadded_text := decrypted[..unpadded_len].bytestr()
println('Decrypted Text: "${unpadded_text}"')
// --- 2. DES Block Cipher ---
println('\n--- DES ---')
des_key := [u8(1), 2, 3, 4, 5, 6, 7, 8] // 8-byte key
des_block := des.new_cipher(des_key)
des_plain := 'DESplain'.bytes() // exactly 8 bytes (DES block size)
mut des_cipher := []u8{len: 8}
des_block.encrypt(mut des_cipher, des_plain)
println('DES Ciphertext (Hex): ${des_cipher.hex()}')
mut des_decrypted := []u8{len: 8}
des_block.decrypt(mut des_decrypted, des_cipher)
println('DES Decrypted: "${des_decrypted.bytestr()}"')
// --- 3. Blowfish Block Cipher ---
println('\n--- Blowfish (Encryption Only) ---')
bf_key := 'blowfish_key'.bytes()
mut bf := blowfish.new_cipher(bf_key) or { panic(err) }
bf_plain := 'bf_block'.bytes() // exactly 8 bytes (Blowfish block size)
mut bf_cipher := []u8{len: 8}
bf.encrypt(mut bf_cipher, bf_plain)
println('Blowfish Ciphertext (Hex): ${bf_cipher.hex()}')
println('(Note: V standard library crypto.blowfish only supports encryption)')
// --- 4. RC4 Stream Cipher ---
println('\n--- RC4 (Stream Cipher) ---')
rc4_key := 'rc4_secret_key'.bytes()
rc4_plain := 'RC4 is a stream cipher commonly used for legacy operations.'.bytes()
mut rc4_enc := rc4.new_cipher(rc4_key) or { panic(err) }
mut rc4_cipher := []u8{len: rc4_plain.len}
rc4_enc.xor_key_stream(mut rc4_cipher, rc4_plain)
println('RC4 Ciphertext (Hex): ${rc4_cipher.hex()}')
mut rc4_dec := rc4.new_cipher(rc4_key) or { panic(err) }
mut rc4_decrypted := []u8{len: rc4_cipher.len}
rc4_dec.xor_key_stream(mut rc4_decrypted, rc4_cipher)
println('RC4 Decrypted: "${rc4_decrypted.bytestr()}"')
}
Log And Crypto
Log And Crypto
V has a very rich and growing standard library and is actively updated. This lesson on Log And Crypto showcases modern standard library packages, system calls, network sockets, inline assembly, or WASM support.
Additional Context from Repository docs:
This example demonstrates the concepts of log and crypto.
module main
import log
import crypto.sha256
import crypto.md5
fn main() {
println('=== Log & Crypto Module Examples ===')
// --- log ---
println('\n--- log ---')
// V's log module provides customizable levels (debug, info, warn, error, fatal)
mut logger := log.Log{}
logger.set_level(.info) // Set threshold (ignores debug level)
logger.info('Logger initialized.')
logger.warn('This is a warning message.')
logger.error('This is an error message.')
// --- crypto ---
println('\n--- crypto ---')
input := 'V language standard library'
// SHA256 Hash
sha_hash := sha256.hexhash(input)
println('SHA-256 of "${input}":')
println(' ${sha_hash}')
// MD5 Hash
md5_hash := md5.hexhash(input)
println('MD5 of "${input}":')
println(' ${md5_hash}')
}
Sync Concurrency
Sync Concurrency
V supports lightweight concurrency using v-routines via the spawn keyword (which spawns a function in a new thread). Threads communicate safely using channels, which prevent race conditions. For shared memory concurrency, V provides the shared keyword alongside lock and unlock blocks to safely synchronize access to variables.
These examples cover spawning tasks, reading/writing channels, buffering, select statements, and thread synchronization.
Additional Context from Repository docs:
This example demonstrates the concepts of sync concurrency.
module main
import sync
import time
fn worker(id int, mut wg sync.WaitGroup) {
defer {
wg.done()
}
println('Worker ${id} starting...')
time.sleep(50 * time.millisecond)
println('Worker ${id} done!')
}
fn main() {
println('=== Sync & Concurrency Examples ===')
// 1. WaitGroup (Wait for multiple goroutines/tasks)
mut wg := sync.new_waitgroup()
for i in 1 .. 4 {
wg.add(1)
go worker(i, mut wg)
}
wg.wait()
println('All workers completed!')
// 2. Mutex (Thread-safe shared state access)
println('\n=== Mutex Demo ===')
mut mu := sync.new_mutex()
mu.@lock()
println('Mutex locked')
mu.unlock()
println('Mutex unlocked')
}
Encoding Formats
Encoding Formats
V has a very rich and growing standard library and is actively updated. This lesson on Encoding Formats showcases modern standard library packages, system calls, network sockets, inline assembly, or WASM support.
Additional Context from Repository docs:
This example demonstrates the concepts of encoding formats.
module main
import encoding.base64
import encoding.hex
import encoding.csv
fn main() {
println('=== Encoding Modules Examples ===')
// --- 1. Base64 ---
println('\n--- Base64 ---')
raw_str := 'V Programming Language'
encoded_b64 := base64.encode_str(raw_str)
println('Encoded Base64: ${encoded_b64}')
decoded_b64 := base64.decode_str(encoded_b64)
println('Decoded Base64: ${decoded_b64}')
// --- 2. Hex ---
println('\n--- Hex ---')
raw_bytes := [u8(72), 101, 108, 108, 111] // "Hello"
encoded_hex := hex.encode(raw_bytes)
println('Encoded Hex: ${encoded_hex}')
decoded_hex := hex.decode(encoded_hex) or { []u8{} }
println('Decoded Hex: ${decoded_hex.bytestr()}')
// --- 3. CSV ---
println('\n--- CSV ---')
csv_data := 'Name,Age,City\nAlice,30,New York\nBob,25,San Francisco'
mut reader := csv.new_reader(csv_data)
println('Reading CSV rows:')
for {
row := reader.read() or { break }
println(' Row: ${row}')
}
}
Arrays Utility
Arrays Utility
V's arrays module provides helpers for searching, grouping, partitioning, folding, and reducing collections. The verified demo in this repository exercises many documented helpers, including extremum lookup, chunking, searching, uniqueness, partitioning, windowing, and rotations.
module main
import arrays
fn main() {
println('=== arrays Utility Module Examples ===')
nums := [5, 3, 9, 1, 7, 3]
sorted_nums := [1, 3, 5, 7, 9]
words := ['apple', 'banana', 'pear', 'banana']
repeated := [1, 1, 2, 2, 3, 3]
letters := ['a', 'b', 'c']
min_val := arrays.min(nums) or { 0 }
max_val := arrays.max(nums) or { 0 }
println('Array: ${nums}')
println('Min: ${min_val}')
println('Max: ${max_val}')
min_idx := arrays.idx_min(nums) or { -1 }
max_idx := arrays.idx_max(nums) or { -1 }
println('Index of Min: ${min_idx}')
println('Index of Max: ${max_idx}')
chunked := arrays.chunk(nums, 2)
println('chunk(): ${chunked}')
append_result := arrays.append(nums, [10, 11])
println('append(): ${append_result}')
concat_result := arrays.concat(nums, 10, 11)
println('concat(): ${concat_result}')
mut copy_result := []int{}
copied_count := arrays.copy(mut copy_result, nums)
println('copy(): ${copied_count} -> ${copy_result}')
distinct_result := arrays.distinct(words)
println('distinct(): ${distinct_result}')
arrays.each(nums, fn (elem int) {
println('each(): ${elem}')
})
arrays.each_indexed(nums, fn (i int, elem int) {
println('each_indexed(): ${i} -> ${elem}')
})
filtered := arrays.filter_indexed(nums, fn (idx int, elem int) bool {
return idx % 2 == 0 && elem > 3
})
println('filter_indexed(): ${filtered}')
first_match := arrays.find_first(nums, fn (elem int) bool {
return elem > 5
}) or { 0 }
println('find_first(): ${first_match}')
last_match := arrays.find_last(nums, fn (elem int) bool {
return elem > 5
}) or { 0 }
println('find_last(): ${last_match}')
flat_mapped := arrays.flat_map[int, string](nums, fn (elem int) []string {
return [elem.str(), '!']
})
println('flat_map(): ${flat_mapped}')
flat_mapped_indexed := arrays.flat_map_indexed[int, string](nums, fn (idx int, elem int) []string {
return ['${idx}', elem.str()]
})
println('flat_map_indexed(): ${flat_mapped_indexed}')
flattened := arrays.flatten([[1, 2], [3, 4], [5]])
println('flatten(): ${flattened}')
folded := arrays.fold(nums, 0, fn (acc int, elem int) int {
return acc + elem
})
println('fold(): ${folded}')
folded_indexed := arrays.fold_indexed(nums, 0, fn (idx int, acc int, elem int) int {
return acc + idx + elem
})
println('fold_indexed(): ${folded_indexed}')
println('group(): skipped in this sample because the helper expects variadic slices and is sensitive to local analyzer parsing')
grouped_by_parity := arrays.group_by(nums, fn (val int) string {
if val % 2 == 0 {
return 'even'
}
return 'odd'
})
println('group_by(): ${grouped_by_parity}')
binary_search_result := arrays.binary_search(sorted_nums, 7) or { -1 }
println('binary_search(): ${binary_search_result}')
c_array_example := unsafe { arrays.carray_to_varray[int](nil, 0) }
println('carray_to_varray(): ${c_array_example}')
chunked_while := arrays.chunk_while(nums, fn (before int, after int) bool {
return before < after
})
println('chunk_while(): ${chunked_while}')
index_first := arrays.index_of_first(nums, fn (idx int, elem int) bool {
return idx > 0 && elem == 3
})
println('index_of_first(): ${index_first}')
index_last := arrays.index_of_last(nums, fn (idx int, elem int) bool {
return idx > 0 && elem == 3
})
println('index_of_last(): ${index_last}')
joined := arrays.join_to_string(words, ' | ', fn (elem string) string {
return elem.to_upper()
})
println('join_to_string(): ${joined}')
lower := arrays.lower_bound(sorted_nums, 4) or { 0 }
upper := arrays.upper_bound(sorted_nums, 4) or { 0 }
println('lower_bound(): ${lower}')
println('upper_bound(): ${upper}')
mapped := arrays.map_indexed(nums, fn (idx int, elem int) int {
return idx + elem
})
println('map_indexed(): ${mapped}')
counts := arrays.map_of_counts([1, 2, 2, 3, 1])
println('map_of_counts(): ${counts}')
indexes := arrays.map_of_indexes([9, 1, 9, 4])
println('map_of_indexes(): ${indexes}')
merge_result := arrays.merge(letters, ['d'])
println('merge(): ${merge_result}')
partition_even, partition_odd := arrays.partition(nums, fn (elem int) bool {
return elem % 2 == 0
})
println('partition(): even=${partition_even}, odd=${partition_odd}')
reduce_result := arrays.reduce(nums, fn (acc int, elem int) int {
return acc + elem
}) or { 0 }
println('reduce(): ${reduce_result}')
reduce_indexed_result := arrays.reduce_indexed(nums, fn (idx int, acc int, elem int) int {
return acc + idx + elem
}) or { 0 }
println('reduce_indexed(): ${reduce_indexed_result}')
mut reverse_iter := arrays.reverse_iterator(nums)
println('reverse_iterator():')
for {
if value := reverse_iter.next() {
println(' ${*value}')
} else {
break
}
}
reverse_iter.free()
mut rotate_left_example := [1, 2, 3, 4, 5]
arrays.rotate_left(mut rotate_left_example, 2)
println('rotate_left(): ${rotate_left_example}')
mut rotate_right_example := [1, 2, 3, 4, 5]
arrays.rotate_right(mut rotate_right_example, 2)
println('rotate_right(): ${rotate_right_example}')
sum_result := arrays.sum(nums) or { 0 }
println('sum(): ${sum_result}')
unique_result := arrays.uniq(repeated)
println('uniq(): ${unique_result}')
uniq_all_result := arrays.uniq_all_repeated(repeated)
println('uniq_all_repeated(): ${uniq_all_result}')
uniq_only_result := arrays.uniq_only(repeated)
println('uniq_only(): ${uniq_only_result}')
uniq_only_repeated_result := arrays.uniq_only_repeated(repeated)
println('uniq_only_repeated(): ${uniq_only_repeated_result}')
window_result := arrays.window(nums, arrays.WindowAttribute{ size: 2, step: 1 })
println('window(): ${window_result}')
println('All arrays examples completed.')
}
This section is intentionally broader than a quick smoke test; it mirrors the runnable repository example and highlights the helpers exposed by the module.
Toml
This example demonstrates how to parse and query TOML configuration files using V's built-in toml module.
module main
import toml
const toml_content = '
# TOML configuration example
title = "V TOML Demo"
[owner]
name = "Antigravity AI"
organization = "Google DeepMind"
[database]
server = "127.0.0.1"
ports = [ 5432, 5433 ]
connection_max = 1000
enabled = true
'
fn main() {
println('=== TOML Module Demo ===')
doc := toml.parse_text(toml_content) or {
println('Failed to parse TOML: ${err}')
return
}
// 1. Reading basic values using value() and type converters
title := doc.value('title').string()
println('Project Title: ${title}')
// 2. Accessing nested tables
owner_name := doc.value('owner.name').string()
org := doc.value('owner.organization').string()
println('Owner: ${owner_name} (${org})')
// 3. Accessing primitive values
server := doc.value('database.server').string()
conn_max := doc.value('database.connection_max').int()
enabled := doc.value('database.enabled').bool()
println('DB Server: ${server} | Connection Max: ${conn_max} | Enabled: ${enabled}')
// 4. Retrieving array values
ports_any := doc.value('database.ports')
println('Ports Any: ${ports_any}')
// Accessing array elements with query syntax
port_0 := doc.value('database.ports[0]').int()
port_1 := doc.value('database.ports[1]').int()
println('Primary Port: ${port_0} | Secondary Port: ${port_1}')
// 5. Using default values for non-existing keys
db_timeout := doc.value('database.timeout').default_to(30).int()
println('Database Timeout (Default): ${db_timeout} seconds')
// 6. Optional retrieval using value_opt()
if db_server := doc.value_opt('database.server') {
println('Optional check: Database server key exists. Value = ${db_server.string()}')
} else {
println('Optional check: Database server key does not exist.')
}
}
Strconv
This example demonstrates how to convert strings to numbers, parse numbers in different bases and bit-sizes, and convert numbers back to base string representations using the strconv module.
module main
import strconv
fn main() {
println('=== strconv Module Demo ===')
// 1. Convert string to integer types (with error propagation/handling)
val_int := strconv.atoi('12345') or {
println('Error parsing int: ${err}')
0
}
println('Parsed int: ${val_int}')
val_i64 := strconv.atoi64('9223372036854775807') or {
println('Error parsing i64: ${err}')
0
}
println('Parsed i64: ${val_i64}')
// 2. Parse unsigned and specific bases/bit-sizes
// parse_int(s string, base int, bit_size int) !i64
val_hex := strconv.parse_int('0xff', 0, 64) or {
println('Error parsing hex: ${err}')
0
}
println('Parsed hex (0xff in base 0): ${val_hex}')
val_bin := strconv.parse_uint('101010', 2, 32) or {
println('Error parsing binary: ${err}')
0
}
println('Parsed binary (101010 in base 2): ${val_bin}')
// 3. Convert string to float (atof64)
val_f64 := strconv.atof64('3.14159265') or {
println('Error parsing f64: ${err}')
0.0
}
println('Parsed float64: ${val_f64}')
// 4. Convert number to base string representation
// format_int(n i64, radix int) string
binary_str := strconv.format_int(42, 2)
hex_str := strconv.format_int(255, 16)
println('42 in binary: ${binary_str}')
println('255 in hex: ${hex_str}')
}
Term
Term
V's standard library provides a direct, cross-platform module named term for querying terminal attributes, altering console text colors/styles, and printing preformatted output badges. Here is the simplest, most practical guide to when you should actually use each of these tools in real-world programming.
1. Terminal Size Metadata
- The Vibe: "Measuring the room size."
- What it does: Tells you how many columns (width) and rows (height) are currently visible in the user's terminal window.
- Best to use when: You are rendering custom terminal layouts, tables, or ASCII art that must fit the screen.
- Real-world example: Wrapping text dynamically so it doesn't spill over the screen edge.
2. ANSI Text Coloring
- The Vibe: "Adding paint to the console."
- What it does: Applies green, red, yellow, or blue styling to terminal characters.
- Best to use when: Highlighting success, errors, warnings, or structural tags.
- Real-world example: Printing error messages in bold red.
3. Styling Modifiers
- The Vibe: "The font options panel."
- What it does: Renders text as bold, underlined, or strikethrough.
- Best to use when: Emphasizing headings, showing links, or marking deprecated options.
- Real-world example: Printing column headers of a CLI table in bold.
4. Background Fills & Combinations
- The Vibe: "The highlighter pen."
- What it does: Fills the text background block with color, and supports mixing foreground styles and background styles.
- Best to use when: Creating alerts, status blocks, or highlighting active menu items.
- Real-world example: Printing a yellow warning status badge with a blue background.
5. Preformatted Messages
- The Vibe: "The instant status stamp."
- What it does: Provides preformatted
[OK],[WARNING], and[FAILED]status boxes with colors built in. - Best to use when: Logging task progress or system bootstrap outputs.
- Real-world example: Printing
[OK] Server started on port 8080.
Additional Context from Repository docs:
This example demonstrates the concepts of term.
module main
import term
fn main() {
println('=== term Module Demo ===')
// ==========================================
// 1. Terminal Size Metadata
// ==========================================
// term.get_terminal_size() returns the (width, height) of the active terminal session in columns and rows.
width, height := term.get_terminal_size()
println('Terminal size: ${width} columns x ${height} rows')
// ==========================================
// 2. Colored Text (Foreground Styling)
// ==========================================
// Foreground color helpers wrap the string with ANSI escape codes to change the text color.
println(term.green('This text is green!'))
println(term.red('This text is red!'))
println(term.yellow('This text is yellow!'))
println(term.blue('This text is blue!'))
// ==========================================
// 3. Text Styles & Modifiers
// ==========================================
// Text modifiers add visual decorations like bold, underline, or strikethrough.
println(term.bold('This text is bold!'))
println(term.underline('This text is underlined!'))
println(term.strikethrough('This text has a strikethrough!'))
// ==========================================
// 4. Background Styling
// ==========================================
// Background color helpers fill the background area behind the printed characters.
println(term.bg_blue(' This has a blue background! '))
// ==========================================
// 5. Mixed Styling & Layering
// ==========================================
// We can combine text color, style (bold/underline), and background color by nesting the calls.
println(term.bg_blue(term.yellow(' Yellow text on a blue background ')))
println(term.bg_red(term.white(term.bold(' Bold white text on a red background '))))
println(term.bg_green(term.black(term.underline(' Underlined black text on a green background '))))
// ==========================================
// 6. Preformatted Status Messages
// ==========================================
// V's term module provides built-in preformatted status message helper templates.
// These automatically print colored status stamps like [OK], [WARNING], or [FAILED] followed by the message.
println(term.ok_message('Operation succeeded!'))
println(term.warn_message('This is a warning!'))
println(term.fail_message('Operation failed!'))
}
Term Ui
Term Ui
V's standard library provides the term.ui (or tui) module for building full-featured, cross-platform terminal user interface applications. It manages an event-driven render loop, keyboard and mouse input handling, window resize events, screen buffer clearing, and drawing text, shapes, interactive GUI-in-TUI controls (inputs, buttons, checkboxes, tabs), and custom RGB colors directly in the terminal window.
1. Configuration & Application Lifecycle Callbacks (`Config`)
- The Vibe: "The central app engine configuration and lifecycle setup."
- What it does: Configures the TUI context via
tui.init()with custom application state and lifecycle callback hooks: user_data voidptr: Pointer to custom application state struct, passed to all callbacks.init_fn fn(voidptr): Callback fired once after terminal initialization before the main loop starts.frame_fn fn(voidptr): Main render callback fired automatically atframe_rateframes per second.event_fn fn(&Event, voidptr): Callback fired for keyboard, mouse, and window resize events.cleanup_fn fn(voidptr): Callback fired once when the application exits to restore terminal state cleanly.fail_fn fn(string): Callback fired if a fatal error occurs during initialization.- Options:
frame_rate(FPS, default 30),buffer_size(input buffer size),hide_cursor(hides terminal cursor),capture_events(intercepts raw key combinations such asCtrl+C/Ctrl+Z),window_title(sets window title bar). - Best to use when: Setting up structured terminal applications with state management, custom render rates, and clean shutdown routines.
- Real-world example: Terminal dashboards, file managers, text editors, and system monitoring monitors.
2. Interactive Controls & GUI-in-TUI Patterns
- The Vibe: "Full GUI-style interactive widgets inside the terminal."
- What it does: Implements common UI controls using mouse bounding boxes and keyboard event dispatchers:
- Interactive Text Input Box: Captures focused key strokes (
.utf8input), backspace deletion (.backspace), Enter submission (.enter), and renders a blinking cursor bar (|). - Mouse-Clickable UI Buttons: Renders styled rectangle buttons with hover state (
.mouse_move) and active pressed state (.mouse_down/.mouse_upinside button boundsx..x+w, y..y+h). - Checkboxes & Toggle Switches: Clickable boolean toggle switches (
[X]vs[ ]) for toggling settings like dark mode or grid lines. - Radio Selectors & Segmented Controls: Single-option selection controls (
(•)vs( )) for choosing modes or speeds. - Navigation Tabs: Tabbed interface bars (
[1] Controls & Form [2] Canvas Drawing [3] Event Log Stream) allowing users to switch active views via macOS-friendly number shortcuts (1,2,3), fallback function keys (F1-F3), or mouse clicks.
3. Graphics & Drawing Primitives
- The Vibe: "The terminal screen canvas paint box."
- What it does: Provides built-in drawing methods for text, lines, and shapes:
draw_text(x, y, text): Renders strings starting at columnx, rowy.draw_point(x, y): Draws a single point/character cell atx,y.draw_line(x1, y1, x2, y2): Draws solid line segments using Bresenham's algorithm or fast horizontal line rendering.draw_dashed_line(x1, y1, x2, y2): Draws dashed line segments.draw_rect(x1, y1, x2, y2): Draws a filled rectangle spanning from top-left(x1, y1)to bottom-right(x2, y2).draw_empty_rect(x1, y1, x2, y2): Draws an outlined rectangle without fill.draw_empty_dashed_rect(x1, y1, x2, y2): Draws a dashed rectangle outline.horizontal_separator(y): Draws a horizontal line rule across the entire window width at rowy.
4. Color & Styling Control (`Color`)
- The Vibe: "The terminal palette and typography controls."
- What it does: Manages foreground/background colors and text attributes:
set_color(Color{r, g, b}): Sets text foreground color (using 24-bit RGB true color or 256-color ANSI fallback).set_bg_color(Color{r, g, b}): Sets background color for text and filled shapes.reset_color()/reset_bg_color(): Restores foreground or background color back to default terminal style.bold(): Enables bold text formatting.reset(): Resets all text styles and color formatting attributes back to default (\x1b[0m).
5. Real-Time Event Loop & Event Stream Inspector (`Event`)
- The Vibe: "The real-time keyboard, mouse, and window event logger."
- What it does: Inspects and logs live input events via
tui.Event: typ EventType:.key_down,.mouse_down,.mouse_up,.mouse_move,.mouse_drag,.mouse_scroll,.resized.- Keyboard details:
code(KeyCodeenum matching.escape,.enter,.space,.up,.down,.left,.right,.tab,._1–._9, letters, numbers),modifiers(.ctrl,.shift,.altflags),ascii,utf8. - Mouse details:
x,ycoordinates,button(.left,.middle,.right),direction(.up,.down,.left,.rightscroll). - Window resize details:
width,heightpassed on.resizedevents (and auto-updated inwindow_width/window_height).
Additional Context from Repository docs:
This example demonstrates the concepts of term.ui.
module main
// Import the terminal UI module from V's standard library
import term.ui as tui
// Button represents an interactive mouse-clickable TUI button
struct Button {
id string
label string
x int
y int
width int
height int
}
// App struct stores complete application state across render frames and events
struct App {
mut:
tui &tui.Context = unsafe { nil }
// Navigation Tabs
active_tab int // 0: Form & Controls, 1: Drawing Primitives, 2: Event Stream Log
tab_titles []string
// Form & Widget State
text_input string
input_focused bool
counter int
show_grid bool
dark_mode bool
selected_option int
radio_options []string
// Buttons list
buttons []Button
// Event Stream Log (last 10 events)
event_log []string
// Mouse tracking
mouse_x int
mouse_y int
hovered_btn string
clicked_btn string
scroll_state string
}
// log_event adds a formatted message to the event log buffer
fn (mut app App) log_event(msg string) {
app.event_log << msg
if app.event_log.len > 10 {
app.event_log.delete(0)
}
}
// frame_fn is called automatically on every render cycle (at 30 FPS)
fn frame_fn(x voidptr) {
mut app := unsafe { &App(x) }
// 1. Clear previous frame contents from screen buffer
app.tui.clear()
// 2. Main Color Scheme depending on Dark Mode toggle
bg_r, bg_g, bg_b := if app.dark_mode { u8(15), u8(18), u8(28) } else { u8(30), u8(45), u8(70) }
accent_r, accent_g, accent_b := u8(0), u8(180), u8(220)
// 3. Render Top Navigation Bar & Tabs
app.tui.set_bg_color(r: bg_r, g: bg_g, b: bg_b)
app.tui.set_color(r: 255, g: 255, b: 255)
app.tui.bold()
header := ' === V term.ui Interactive Widgets & Event Inspector === [Res: ${app.tui.window_width}x${app.tui.window_height}] '
app.tui.draw_text(2, 1, header)
app.tui.reset()
// Draw Tab Buttons (using macOS-friendly standard 1, 2, 3 shortcuts)
mut tab_x := 4
for i in 0 .. app.tab_titles.len {
title := app.tab_titles[i]
if i == app.active_tab {
app.tui.set_bg_color(r: accent_r, g: accent_g, b: accent_b)
app.tui.set_color(r: 255, g: 255, b: 255)
app.tui.bold()
} else {
app.tui.set_bg_color(r: 60, g: 70, b: 90)
app.tui.set_color(r: 200, g: 200, b: 200)
}
tab_btn_text := ' [ ${i + 1} ] ${title} '
app.tui.draw_text(tab_x, 3, tab_btn_text)
app.tui.reset()
tab_x += tab_btn_text.len + 2
}
app.tui.horizontal_separator(4)
// 4. Render Active Tab Content
match app.active_tab {
0 {
// ==========================================
// TAB 0: Interactive Form, Textbox & Buttons
// ==========================================
app.tui.set_color(r: 255, g: 220, b: 0)
app.tui.bold()
app.tui.draw_text(4, 6, '1. Interactive Text Input Box (Click or Press TAB to focus)')
app.tui.reset()
// Textbox Container
input_bg_r, input_bg_g, input_bg_b := if app.input_focused {
u8(40), u8(60), u8(100)
} else {
u8(25), u8(30), u8(45)
}
app.tui.set_bg_color(r: input_bg_r, g: input_bg_g, b: input_bg_b)
app.tui.set_color(r: 255, g: 255, b: 255)
app.tui.draw_rect(4, 7, 54, 9)
cursor_char := if app.input_focused && (app.tui.frame_count / 15) % 2 == 0 {
'|'
} else {
''
}
display_text := if app.text_input == '' {
'Type text here...'
} else {
app.text_input
}
app.tui.draw_text(6, 8, '> ${display_text}${cursor_char}')
app.tui.reset()
// Interactive Buttons Section
app.tui.set_color(r: 255, g: 220, b: 0)
app.tui.bold()
app.tui.draw_text(4, 11, '2. Clickable UI Buttons & Counter State')
app.tui.reset()
// Render Buttons
for btn in app.buttons {
is_hover := app.hovered_btn == btn.id
is_click := app.clicked_btn == btn.id
b_r, b_g, b_b := if is_click {
u8(255), u8(140), u8(0)
} else if is_hover {
u8(0), u8(150), u8(220)
} else {
u8(50), u8(70), u8(100)
}
app.tui.set_bg_color(r: b_r, g: b_g, b: b_b)
app.tui.set_color(r: 255, g: 255, b: 255)
app.tui.bold()
app.tui.draw_rect(btn.x, btn.y, btn.x + btn.width, btn.y + btn.height)
app.tui.draw_text(btn.x + 2, btn.y + 1, btn.label)
app.tui.reset()
}
// Display Counter Value
app.tui.set_color(r: 0, g: 255, b: 180)
app.tui.bold()
app.tui.draw_text(4, 15, 'Current Counter Value: ${app.counter}')
app.tui.reset()
// Checkboxes & Radio Selectors Section
app.tui.set_color(r: 255, g: 220, b: 0)
app.tui.bold()
app.tui.draw_text(4, 17, '3. Checkbox & Radio Controls')
app.tui.reset()
chk_grid := if app.show_grid { '[X]' } else { '[ ]' }
chk_dark := if app.dark_mode { '[X]' } else { '[ ]' }
app.tui.draw_text(4, 18, '${chk_grid} Show Grid (Click to toggle)')
app.tui.draw_text(32, 18, '${chk_dark} Dark Mode Theme (Click to toggle)')
app.tui.draw_text(4, 20, 'Select Speed Mode:')
for idx, opt in app.radio_options {
selected_str := if idx == app.selected_option { '(•)' } else { '( )' }
app.tui.draw_text(4 + idx * 16, 21, '${selected_str} ${opt}')
}
}
1 {
// ==========================================
// TAB 1: Graphics & Drawing Canvas
// ==========================================
app.tui.set_color(r: 0, g: 220, b: 255)
app.tui.bold()
app.tui.draw_text(4, 6, '=== Canvas Graphics & Drawing Primitives ===')
app.tui.reset()
// Filled Rect
app.tui.set_bg_color(r: 180, g: 40, b: 80)
app.tui.draw_rect(4, 8, 30, 12)
app.tui.reset_bg_color()
app.tui.set_color(r: 255, g: 255, b: 255)
app.tui.draw_text(6, 10, 'Filled Rect (draw_rect)')
// Outline Rect
app.tui.set_color(r: 0, g: 255, b: 150)
app.tui.draw_empty_rect(34, 8, 60, 12)
app.tui.draw_text(36, 10, 'Outline Rect (draw_empty_rect)')
// Dashed Line & Dashed Rect
app.tui.set_color(r: 255, g: 200, b: 0)
app.tui.draw_dashed_line(4, 14, 30, 14)
app.tui.draw_text(4, 15, 'Dashed Line (draw_dashed_line)')
app.tui.draw_empty_dashed_rect(34, 14, 60, 17)
app.tui.draw_text(36, 15, 'Dashed Rect (draw_empty_dashed_rect)')
app.tui.reset()
}
else {
// ==========================================
// TAB 2: Real-time Event Log Inspector
// ==========================================
app.tui.set_color(r: 255, g: 180, b: 0)
app.tui.bold()
app.tui.draw_text(4, 6, '=== Live Input Event Stream (Last 10 Events) ===')
app.tui.reset()
app.tui.draw_empty_rect(4, 7, 85, 19)
for i, log_entry in app.event_log {
app.tui.set_color(r: 200, g: 220, b: 255)
app.tui.draw_text(6, 8 + i, '[#${i + 1}] ${log_entry}')
}
app.tui.reset()
}
}
// 5. Footer Status & macOS-friendly Shortcuts
app.tui.horizontal_separator(21)
app.tui.set_color(r: 180, g: 180, b: 180)
app.tui.draw_text(4, 22, 'Mouse Pos: X=${app.mouse_x}, Y=${app.mouse_y} | Scroll: ${app.scroll_state} | Active Hover: "${app.hovered_btn}"')
app.tui.draw_text(4, 23, 'Shortcuts: [1-3] Switch Tabs | [TAB] Focus Textbox | [ESC] or "q" Quit')
app.tui.reset()
app.tui.set_cursor_position(0, 0)
app.tui.reset()
app.tui.flush()
}
// event_fn handles keyboard, mouse, and window resize events
fn event_fn(e &tui.Event, x voidptr) {
mut app := unsafe { &App(x) }
match e.typ {
.key_down {
app.log_event('Key Down: Code=${e.code} (${int(e.code)}) | Modifiers=${e.modifiers} | Utf8="${e.utf8}"')
// Mac-friendly Tab switching shortcuts (1, 2, 3 or Escape/q for quit)
match e.code {
.escape, .q {
if !app.input_focused || e.code == .escape {
exit(0)
}
}
.tab {
app.input_focused = !app.input_focused
}
._1, .f1 {
if !app.input_focused || e.code == .f1 {
app.active_tab = 0
}
}
._2, .f2 {
if !app.input_focused || e.code == .f2 {
app.active_tab = 1
}
}
._3, .f3 {
if !app.input_focused || e.code == .f3 {
app.active_tab = 2
}
}
else {}
}
// Textbox Input Editing
if app.input_focused {
match e.code {
.backspace {
if app.text_input.len > 0 {
app.text_input = app.text_input[..app.text_input.len - 1]
}
}
.enter {
app.log_event('Submitted Text: "${app.text_input}"')
}
else {
if e.utf8.len > 0 && e.code != .tab && e.code != .escape {
app.text_input += e.utf8
}
}
}
}
}
.mouse_move {
app.mouse_x = e.x
app.mouse_y = e.y
// Detect button hover
mut found_hover := ''
for btn in app.buttons {
if app.active_tab == 0 && e.x >= btn.x && e.x <= btn.x + btn.width
&& e.y >= btn.y && e.y <= btn.y + btn.height {
found_hover = btn.id
break
}
}
app.hovered_btn = found_hover
}
.mouse_down {
app.mouse_x = e.x
app.mouse_y = e.y
app.log_event('Mouse Click: Btn=${e.button} at (${e.x}, ${e.y})')
// 1. Check Tab Clicks
if e.y == 3 {
if e.x >= 4 && e.x <= 18 {
app.active_tab = 0
} else if e.x >= 20 && e.x <= 36 {
app.active_tab = 1
} else if e.x >= 38 && e.x <= 56 {
app.active_tab = 2
}
}
// 2. Check Textbox Focus Click
if app.active_tab == 0 && e.x >= 4 && e.x <= 54 && e.y >= 7 && e.y <= 9 {
app.input_focused = true
} else if app.active_tab == 0 && (e.y < 7 || e.y > 9) {
app.input_focused = false
}
// 3. Check Button Clicks
if app.active_tab == 0 {
for btn in app.buttons {
if e.x >= btn.x && e.x <= btn.x + btn.width && e.y >= btn.y
&& e.y <= btn.y + btn.height {
app.clicked_btn = btn.id
match btn.id {
'inc' {
app.counter++
app.log_event('Button Click: Counter Incremented to ${app.counter}')
}
'dec' {
app.counter--
app.log_event('Button Click: Counter Decremented to ${app.counter}')
}
'clear' {
app.text_input = ''
app.log_event('Button Click: Text Input Cleared')
}
'reset' {
app.counter = 0
app.log_event('Button Click: Counter Reset to 0')
}
else {}
}
break
}
}
// Checkbox Toggles
if e.y == 18 {
if e.x >= 4 && e.x <= 20 {
app.show_grid = !app.show_grid
app.log_event('Toggle Grid: ${app.show_grid}')
} else if e.x >= 32 && e.x <= 52 {
app.dark_mode = !app.dark_mode
app.log_event('Toggle Dark Mode: ${app.dark_mode}')
}
}
// Radio Options
if e.y == 21 {
if e.x >= 4 && e.x <= 16 {
app.selected_option = 0
app.log_event('Selected Speed: Slow')
} else if e.x >= 20 && e.x <= 32 {
app.selected_option = 1
app.log_event('Selected Speed: Normal')
} else if e.x >= 36 && e.x <= 48 {
app.selected_option = 2
app.log_event('Selected Speed: Fast')
}
}
}
}
.mouse_up {
app.clicked_btn = ''
}
.mouse_scroll {
app.scroll_state = '${e.direction}'
app.log_event('Mouse Scroll: Direction=${e.direction} at (${e.x}, ${e.y})')
}
.resized {
app.log_event('Window Resized: Width=${app.tui.window_width}, Height=${app.tui.window_height}')
}
else {}
}
}
fn main() {
mut app := &App{
tab_titles: ['Controls & Form', 'Canvas Drawing', 'Event Log Stream']
text_input: 'Hello Vlang term.ui!'
counter: 10
show_grid: true
dark_mode: true
radio_options: ['Slow', 'Normal', 'Fast']
buttons: [
Button{
id: 'inc'
label: '[ + ] Increment'
x: 4
y: 12
width: 16
height: 2
},
Button{
id: 'dec'
label: '[ - ] Decrement'
x: 22
y: 12
width: 16
height: 2
},
Button{
id: 'reset'
label: '[ R ] Reset'
x: 40
y: 12
width: 12
height: 2
},
Button{
id: 'clear'
label: '[ C ] Clear Text'
x: 54
y: 12
width: 16
height: 2
},
]
}
app.tui = tui.init(
user_data: app
frame_fn: frame_fn
event_fn: event_fn
window_title: 'V Comprehensive Terminal GUI & Widgets'
hide_cursor: true
capture_events: true
frame_rate: 30
buffer_size: 256
)
app.tui.run()!
}
Benchmark
This example demonstrates timing code execution chunks and step-by-step progress benchmarking using the benchmark module.
module main
import benchmark
import time
fn main() {
println('=== benchmark Module Demo ===')
// Example 1: Using benchmark.start() and measure()
println('--- Simple Measurement ---')
mut b := benchmark.start()
// Simulate work chunk 1
time.sleep(50 * time.millisecond)
b.measure('Simulated task 1 (50ms sleep)')
// Simulate work chunk 2
time.sleep(100 * time.millisecond)
b.measure('Simulated task 2 (100ms sleep)')
// Example 2: Using structured new_benchmark()
println('\n--- Structured Step-by-Step Benchmarking ---')
mut bmark := benchmark.new_benchmark()
// Step 1: Ok step
bmark.step()
time.sleep(30 * time.millisecond)
bmark.ok()
println(bmark.step_message('Step 1 (successful arithmetic)'))
// Step 2: Failed step demo
bmark.step()
time.sleep(10 * time.millisecond)
bmark.fail()
println(bmark.step_message('Step 2 (simulated failure verification)'))
// Finalize and print results summary
bmark.stop()
println(bmark.total_message('Final summary of execution stages'))
}
Clipboard
This example demonstrates writing to and reading from the system clipboard on macOS using the clipboard module, including a backup and restore mechanism.
module main
import clipboard
fn main() {
println('=== clipboard Module Demo ===')
// 1. Initialize clipboard
mut cb := clipboard.new()
defer {
cb.destroy()
}
if !cb.is_available() {
println('Clipboard is not available on this platform/session.')
return
}
// 2. Backup current clipboard content so we do not overwrite user data permanently
original_text := cb.paste()
println('Backed up original clipboard text (length: ${original_text.len})')
// 3. Copy new text to clipboard
test_message := 'Hello from Vlang standard library!'
println('Copying text to clipboard: "${test_message}"')
if cb.copy(test_message) {
println('Text successfully copied!')
} else {
println('Failed to copy text.')
}
// 4. Paste back to verify
pasted_text := cb.paste()
println('Pasted text from clipboard: "${pasted_text}"')
// 5. Restore original clipboard content
println('Restoring original clipboard content...')
cb.copy(original_text)
println('Clipboard restore completed successfully.')
}
Semver
This example demonstrates parsing semantic versions and checking constraint satisfaction using the semver module.
module main
import semver
fn main() {
println('=== semver Module Demo ===')
// 1. Parsing semver strings
v1 := semver.from('1.5.0-beta.1+build.123') or {
println('Failed to parse version: ${err}')
return
}
v2 := semver.from('1.5.0') or {
println('Failed to parse version: ${err}')
return
}
v3 := semver.from('2.0.0-rc.1') or {
println('Failed to parse version: ${err}')
return
}
// 2. Accessing parts of the version struct
println('Version 1 components:')
println(' Raw string: ${v1.str()}')
println(' Major: ${v1.major}')
println(' Minor: ${v1.minor}')
println(' Patch: ${v1.patch}')
println(' Prerelease: ${v1.prerelease}')
println(' Build info: ${v1.metadata}')
// 3. Comparisons using relational operators
println('\nVersion Comparisons:')
println(' ${v1} < ${v2} ? -> ${v1 < v2}')
println(' ${v2} > ${v1} ? -> ${v2 > v1}')
println(' ${v3} >= ${v2} ? -> ${v3 >= v2}')
// 4. Checking against version ranges (constraints)
println('\nVersion Constraint Satisfactions:')
// Range checking
println(' Is ${v2} in range ">=1.0.0 <2.0.0" ? -> ${v2.satisfies('>=1.0.0 <2.0.0')}')
println(' Is ${v3} in range ">=1.0.0 <2.0.0" ? -> ${v3.satisfies('>=1.0.0 <2.0.0')}')
// Complex constraint checking using logical OR (||)
range_query := '^1.4.0 || >=2.0.0'
println(' Does ${v2} satisfy "${range_query}"? -> ${v2.satisfies(range_query)}')
println(' Does ${v3} satisfy "${range_query}"? -> ${v3.satisfies(range_query)}')
}
Maps Standard Library Module (maps.v)
This example demonstrates the high-level helpers in V's maps module, including filtering, transforming, inverting, merging, and converting between maps and arrays. The repository version is a verified walkthrough that exercises the full set of documented helpers.
module main
import maps
fn main() {
println('=== maps Module Demo ===')
m1 := {
'apple': 1
'banana': 2
'cherry': 3
}
filtered := maps.filter(m1, fn (k string, v int) bool {
return v > 1
})
println('filter(): ${filtered}')
keys_upper := maps.to_array(m1, fn (k string, v int) string {
return k.to_upper()
})
println('to_array(): ${keys_upper}')
inverted := maps.invert(m1)
println('invert(): ${inverted}')
fruits := ['apple', 'banana', 'cherry']
map_from_arr := maps.from_array(fruits)
println('from_array(): ${map_from_arr}')
m2 := {
'banana': 20
'date': 4
}
merged := maps.merge(m1, m2)
println('merge(): ${merged}')
mut mut_map := {
'a': 1
}
maps.merge_in_place(mut mut_map, {
'b': 2
'c': 3
})
println('merge_in_place(): ${mut_map}')
flat_items := maps.flat_map[string, int, string](m1, fn (k string, v int) []string {
return [k, v.str()]
})
println('flat_map(): ${flat_items}')
transformed := maps.to_map[string, int, string, int](m1, fn (k string, v int) (string, int) {
return k.to_upper(), v * 10
})
println('to_map(): ${transformed}')
}
Context
This example demonstrates propagating request-scoped values, cancellation signals, and timeouts across thread boundaries using the context module.
module main
import context
import time
fn main() {
println('=== context Module Demo ===')
// 1. Context with Value
// Useful for passing metadata (e.g. Request ID) through call chains
mut ctx_bg := context.background()
mut ctx_val := context.with_value(ctx_bg, 'request_id', 'REQ-101')
if req_id := ctx_val.value('request_id') {
if req_id is string {
println('Request ID in context: ${req_id}')
}
}
// 2. Context with Cancellation
mut ctx_cancel, cancel := context.with_cancel(mut ctx_val)
// Check if canceled
println('Before cancel - Done channel is open')
cancel() // trigger cancellation
// Select block to read from done channel
done_ch := ctx_cancel.done()
select {
_ := <-done_ch {
println('Context cancellation detected successfully!')
}
1 * time.second {
println('Timeout waiting for cancellation.')
}
}
// 3. Context with Timeout
// Abandon execution after a duration
mut ctx_timeout, cancel_timeout := context.with_timeout(mut ctx_bg, 50 * time.millisecond)
defer {
cancel_timeout()
}
println('Waiting for context timeout (50ms)...')
start := time.now()
timeout_ch := ctx_timeout.done()
select {
_ := <-timeout_ch {
elapsed := time.since(start)
println('Timeout triggered after ${elapsed.milliseconds()} ms!')
}
1 * time.second {
println('Error: Timeout did not trigger in time.')
}
}
}
Archive Tar
This example demonstrates reading and inspecting the contents of .tar.gz files using the archive.tar module.
module main
import archive.tar
import os
// CustomReader implements the tar.Reader interface
struct CustomReader {
pub mut:
files_found int
}
fn (mut cr CustomReader) dir_block(mut read tar.Read, size u64) {
println('Directory in tar: ${read.get_path()}')
}
fn (mut cr CustomReader) file_block(mut read tar.Read, size u64) {
println('File in tar: ${read.get_path()} (${size} bytes)')
cr.files_found++
}
fn (mut cr CustomReader) data_block(mut read tar.Read, data []u8, pending int) {
// Trim content bytes to display them cleanly
content := data.bytestr().trim_space()
println(' Content snippet: "${content}"')
}
fn (mut cr CustomReader) other_block(mut read tar.Read, details string) {
// Ignore details for this demo
}
fn main() {
println('=== archive.tar Module Demo ===')
// 1. Create a dummy file to archive
temp_file := 'temp_file_for_tar.txt'
os.write_file(temp_file, 'Hello standard archive tar from Vlang!') or {
println('Failed to write temp file: ${err}')
return
}
defer {
os.rm(temp_file) or {}
}
// 2. Create the tar.gz archive using system tar
tar_archive := 'temp_archive.tar.gz'
println('Creating tar archive using system tar...')
tar_cmd := if os.user_os() == 'macos' { 'COPYFILE_DISABLE=1 tar -czf' } else { 'tar -czf' }
os.execute('${tar_cmd} ${tar_archive} ${temp_file}')
defer {
os.rm(tar_archive) or {}
}
// 3. Read and parse the tar.gz archive using V's archive.tar module
println('Reading archive using vlib/archive/tar:')
mut reader := CustomReader{}
// Read and parse
tar.read_tar_gz_file(tar_archive, reader) or {
println('Failed to read tar archive: ${err}')
return
}
println('Total files found in archive: ${reader.files_found}')
}
Compress Deflate
This example demonstrates standard Deflate byte stream compression and decompression using the compress.deflate module.
module main
import compress.deflate
fn main() {
println('=== compress.deflate Module Demo ===')
// 1. Data to compress
original_text := 'V programming language standard library deflate compression demo. Deflate is a lossless data compression algorithm.'
println('Original Text length: ${original_text.len} bytes')
// 2. Compress the data using deflate.compress
compressed_bytes := deflate.compress(original_text.bytes()) or {
println('Compression failed: ${err}')
return
}
println('Compressed size: ${compressed_bytes.len} bytes')
// 3. Decompress the data using deflate.decompress
decompressed_bytes := deflate.decompress(compressed_bytes) or {
println('Decompression failed: ${err}')
return
}
println('Decompressed size: ${decompressed_bytes.len} bytes')
// 4. Verify the result
decompressed_text := decompressed_bytes.bytestr()
println('Decompressed text equals original? -> ${decompressed_text == original_text}')
println('Decompressed Text: "${decompressed_text}"')
}
Compress Gzip
This example demonstrates compressing and decompressing binary or text data using the compress.gzip module.
module main
import compress.gzip
fn main() {
println('=== compress.gzip Module Demo ===')
// 1. Original text data
original_text := 'V programming language standard library gzip compression and decompression demonstration. This string is long enough to show compression.'
println('Original Text length: ${original_text.len} bytes')
// 2. Compress the data
compressed_bytes := gzip.compress(original_text.bytes()) or {
println('Compression failed: ${err}')
return
}
println('Compressed size: ${compressed_bytes.len} bytes')
// 3. Decompress the data
decompressed_bytes := gzip.decompress(compressed_bytes) or {
println('Decompression failed: ${err}')
return
}
println('Decompressed size: ${decompressed_bytes.len} bytes')
// 4. Convert back to string and verify
decompressed_text := decompressed_bytes.bytestr()
println('Decompressed text equals original? -> ${decompressed_text == original_text}')
println('Decompressed Text: "${decompressed_text}"')
}
Compress Szip
This example demonstrates packaging multiple files into zip archives recursively, inspecting zip contents/meta-data (size, CRC32), and extracting zip files to folders using the compress.szip module.
module main
import os
import compress.szip
fn main() {
println('=== compress.szip Module Demo ===')
zip_filename := 'demo_archive.zip'
dest_dir := 'extracted_demo'
// Ensure cleanup of any temp files/folders
defer {
os.rm(zip_filename) or {}
os.rmdir_all(dest_dir) or {}
println('\nCleaned up archive and extraction folder.')
}
println('\n--- 1. Creating a Zip Archive ---')
// Open a new zip archive for writing (creating it)
mut archive := szip.open(zip_filename, .default_compression, .write) or {
println('Failed to create zip: ${err}')
return
}
// Add first file entry
archive.open_entry('first_file.txt') or {
println('Failed to open entry: ${err}')
return
}
archive.write_entry('Hello from the first file inside our zip archive!'.bytes()) or {
println('Failed to write entry: ${err}')
return
}
archive.close_entry()
// Add second file entry
archive.open_entry('docs/second_file.txt') or {
println('Failed to open entry: ${err}')
return
}
archive.write_entry('This is a second file nested inside a docs directory.'.bytes()) or {
println('Failed to write entry: ${err}')
return
}
archive.close_entry()
// Close the zip file
archive.close()
println('Successfully created zip archive "${zip_filename}" with 2 entries.')
println('\n--- 2. Inspecting the Zip Archive ---')
// Open zip file in read-only mode to inspect its contents
mut reader := szip.open(zip_filename, .default_compression, .read_only) or {
println('Failed to open zip for reading: ${err}')
return
}
total_entries := reader.total() or { 0 }
println('Total entries found in zip: ${total_entries}')
// Inspect first entry details
reader.open_entry_by_index(0) or {
println('Failed to open entry 0: ${err}')
return
}
name := reader.name()
size := reader.size()
crc := reader.crc32()
println('Entry 0 details -> Name: "${name}", Size: ${size} bytes, CRC32: ${crc}')
reader.close_entry()
reader.close()
println('\n--- 3. Extracting Zip Archive contents to Directory ---')
os.mkdir(dest_dir) or {
println('Failed to create destination directory: ${err}')
return
}
// Extract the full archive to the target folder
success := szip.extract_zip_to_dir(zip_filename, dest_dir) or {
println('Extraction failed: ${err}')
return
}
if success {
println('Successfully extracted all entries to folder "${dest_dir}".')
// Read and display content from extracted files
file1_content := os.read_file(os.join_path(dest_dir, 'first_file.txt')) or { '' }
file2_content := os.read_file(os.join_path(dest_dir, 'docs', 'second_file.txt')) or { '' }
println('Extracted first_file.txt: "${file1_content}"')
println('Extracted docs/second_file.txt: "${file2_content}"')
} else {
println('Extraction reported failure.')
}
}
Compress Zlib
This example demonstrates standard Zlib byte stream compression and decompression using the compress.zlib module.
module main
import compress.zlib
fn main() {
println('=== compress.zlib Module Demo ===')
// 1. Data to compress
original_text := 'V programming language standard library zlib compression demo. Zlib uses the deflate algorithm with headers and checksum.'
println('Original Text length: ${original_text.len} bytes')
// 2. Compress the data using zlib.compress
compressed_bytes := zlib.compress(original_text.bytes()) or {
println('Compression failed: ${err}')
return
}
println('Compressed size: ${compressed_bytes.len} bytes')
// 3. Decompress the data using zlib.decompress
decompressed_bytes := zlib.decompress(compressed_bytes) or {
println('Decompression failed: ${err}')
return
}
println('Decompressed size: ${decompressed_bytes.len} bytes')
// 4. Verify the result
decompressed_text := decompressed_bytes.bytestr()
println('Decompressed text equals original? -> ${decompressed_text == original_text}')
println('Decompressed Text: "${decompressed_text}"')
}
Compress Zstd
This example demonstrates Zstd compression and decompression using the fast Facebook Zstandard algorithm in the compress.zstd module.
module main
import compress.zstd
fn main() {
println('=== compress.zstd Module Demo ===')
// 1. Check version details
version := zstd.version_string()
println('ZSTD Library Version: ${version}')
// 2. Data to compress
original_text := 'Zstd, short for Zstandard, is a fast lossless compression algorithm developed by Facebook. It offers high compression ratios.'
println('\nOriginal Text length: ${original_text.len} bytes')
// 3. Compress using zstd.compress (specifying standard parameters)
compressed_bytes := zstd.compress(original_text.bytes(), compression_level: 3) or {
println('Compression failed: ${err}')
return
}
println('Compressed size: ${compressed_bytes.len} bytes')
// 4. Decompress using zstd.decompress
decompressed_bytes := zstd.decompress(compressed_bytes) or {
println('Decompression failed: ${err}')
return
}
println('Decompressed size: ${decompressed_bytes.len} bytes')
// 5. Verify and display result
decompressed_text := decompressed_bytes.bytestr()
println('Decompressed text equals original? -> ${decompressed_text == original_text}')
println('Decompressed Text: "${decompressed_text}"')
}
Io Fs
This example demonstrates how os.File implements io.Reader and io.Writer interfaces, allowing standard file operations to utilize stream-oriented utilities like io.cp and io.BufferedReader.
module main
import os
import io
fn main() {
println('=== io & File System (os.File) Demo ===')
src_path := 'temp_src_file.txt'
dst_path := 'temp_dst_file.txt'
// Ensure files are cleaned up on completion
defer {
os.rm(src_path) or {}
os.rm(dst_path) or {}
println('\nCleaned up temporary files.')
}
println('\n--- 1. Creating and Writing to File via io.Writer ---')
// os.create returns an os.File struct, which implements the io.Writer interface.
mut src_file := os.create(src_path) or {
println('Failed to create file: ${err}')
return
}
// Write data using the io.Writer write() method
content_to_write := 'Hello! This is a file system demo.\nIt demonstrates how os.File integrates with the io module.\n'
written_bytes := src_file.write(content_to_write.bytes()) or {
println('Failed to write to file: ${err}')
return
}
println('Wrote ${written_bytes} bytes to "${src_path}" using the io.Writer interface.')
src_file.close()
println('\n--- 2. Reading from File via io.BufferedReader ---')
// os.open opens a file for reading, returning an os.File (which implements io.Reader).
mut read_file := os.open(src_path) or {
println('Failed to open file: ${err}')
return
}
// Wrap os.File in io.BufferedReader for convenient line-by-line reading
mut buf_reader := io.new_buffered_reader(reader: read_file)
// Read lines until EOF
for {
line := buf_reader.read_line() or { break }
println('Buffered Read Line: "${line}"')
}
read_file.close()
println('\n--- 3. Copying File Contents using io.cp ---')
// Re-open source file for reading (implements io.Reader)
mut src_to_copy := os.open(src_path) or {
println('Failed to open source file: ${err}')
return
}
defer { src_to_copy.close() }
// Create a new destination file for writing (implements io.Writer)
mut dst_file := os.create(dst_path) or {
println('Failed to create destination file: ${err}')
return
}
defer { dst_file.close() }
// Copy all contents from reader to writer using io.cp
io.cp(mut src_to_copy, mut dst_file) or {
println('Failed to copy file content: ${err}')
return
}
// Explicitly close files so data is flushed and readable
src_to_copy.close()
dst_file.close()
println('Copied content from "${src_path}" to "${dst_path}" via io.cp.')
// Verify the destination file contents
copied_content := os.read_file(dst_path) or {
println('Failed to read destination file: ${err}')
return
}
println('Copied File Contents:\n${copied_content.trim_space()}')
}
Io
This example demonstrates implementing custom Reader and Writer structs and using the io.cp utility to copy data between streams using the io module.
module main
import io
// SimpleReader implements the io.Reader interface
struct SimpleReader {
data string
mut:
pos int
}
fn (mut sr SimpleReader) read(mut buf []u8) !int {
if sr.pos >= sr.data.len {
// Return io.Eof when the end of the stream is reached
return io.Eof{}
}
mut bytes_read := 0
for sr.pos < sr.data.len && bytes_read < buf.len {
buf[bytes_read] = sr.data[sr.pos]
sr.pos++
bytes_read++
}
return bytes_read
}
// SimpleWriter implements the io.Writer interface
struct SimpleWriter {
mut:
buf []u8
}
fn (mut sw SimpleWriter) write(buf []u8) !int {
sw.buf << buf
return buf.len
}
fn main() {
println('=== io Module Demo ===')
// 1. Initialize Reader and Writer
mut reader := SimpleReader{
data: 'Vlang standard library: io package demo.'
}
mut writer := SimpleWriter{}
// 2. Use io.cp to copy data from Reader to Writer
println('Copying data from custom Reader to custom Writer via io.cp...')
io.cp(mut reader, mut writer) or {
println('Error copying data: ${err}')
return
}
// 3. Print the written data
written_str := writer.buf.bytestr()
println('Writer received: "${written_str}"')
}
Io Util
This example demonstrates using the io.util module for creating, writing to, reading from, and cleaning up temporary files and directories.
module main
import os
import io.util
fn main() {
println('=== io.util Module Demo ===')
println('\n--- 1. Creating a Temporary File ---')
// Create a temporary file. The '*' character in the pattern is replaced by a random number.
// tfo.path defaults to the system temp directory if not specified.
mut temp_file, temp_file_path := util.temp_file(pattern: 'v_guide_demo_*.txt') or {
println('Failed to create temp file: ${err}')
return
}
// Close the returned file handle immediately so we can open it with a clean write mode
temp_file.close()
defer {
os.rm(temp_file_path) or {}
println('Cleaned up temporary file: ${temp_file_path}')
}
println('Created temporary file at: ${temp_file_path}')
// Reopen the temp file for writing explicitly
mut f := os.open_file(temp_file_path, 'w') or {
println('Failed to open temp file for writing: ${err}')
return
}
// Write content into the temporary file
f.write('This is some temporary data written to a temp file.'.bytes()) or {
println('Failed to write to temp file: ${err}')
f.close()
return
}
f.close() // Close file to flush buffer and allow reading
// Read content to verify
content := os.read_file(temp_file_path) or {
println('Failed to read temp file: ${err}')
return
}
println('Temp file content: "${content}"')
println('\n--- 2. Creating a Temporary Directory ---')
// Create a temporary directory using a pattern
temp_dir_path := util.temp_dir(pattern: 'v_guide_dir_*') or {
println('Failed to create temp directory: ${err}')
return
}
// Register cleanup on function exit
defer {
os.rmdir_all(temp_dir_path) or {}
println('Cleaned up temporary directory: ${temp_dir_path}')
}
println('Created temporary directory at: ${temp_dir_path}')
// Create a sub-file inside the temporary directory
sub_file_path := os.join_path(temp_dir_path, 'sub_file.txt')
os.write_file(sub_file_path, 'Data saved inside temporary directory.') or {
println('Failed to create sub-file: ${err}')
return
}
println('Created sub-file: ${sub_file_path}')
// List files in the temporary directory to verify
dir_files := os.ls(temp_dir_path) or {
println('Failed to list temp directory: ${err}')
return
}
println('Temporary directory contents: ${dir_files}')
}
Hash
This example demonstrates calculating FNV-1a (32-bit and 64-bit) hashes and CRC32 checksums using the hash module.
module main
import hash.fnv1a
import hash.crc32
fn main() {
println('=== hash Module Demo ===')
input := 'V language standard library'
println('Input string: "${input}"')
// 1. FNV-1a 32-bit and 64-bit string hashing
fnv_32 := fnv1a.sum32_string(input)
fnv_64 := fnv1a.sum64_string(input)
println('FNV-1a 32-bit hash: ${fnv_32}')
println('FNV-1a 64-bit hash: ${fnv_64}')
// 2. CRC32 IEEE checksum
crc_val := crc32.sum(input.bytes())
println('CRC32 checksum: ${crc_val}')
}
Bitfield
This example demonstrates creating bitfields, getting/setting individual bits, performing logical operations (AND, OR, XOR, NOT), and converting to string representation using the bitfield module.
module main
import bitfield
fn main() {
println('=== bitfield Module Demo ===')
// 1. Create bitfield from string
mut bf1 := bitfield.from_str('101100')
println('BitField 1 (from str): ${bf1.str()}')
println('Size of BitField 1: ${bf1.get_size()}')
println('Number of 1s (pop_count): ${bf1.pop_count()}')
// 2. Accessing and modifying individual bits
println('\nModifying individual bits:')
println(' Bit at index 1 before: ${bf1.get_bit(1)}')
bf1.set_bit(1)
println(' Bit at index 1 after set: ${bf1.get_bit(1)}')
bf1.clear_bit(0)
println(' Bitfield after changes: ${bf1.str()}')
// 3. Logical bitwise operations
mut bf2 := bitfield.from_str('011010')
println('\nLogical operations on ${bf1.str()} and ${bf2.str()}:')
and_result := bitfield.bf_and(bf1, bf2)
or_result := bitfield.bf_or(bf1, bf2)
xor_result := bitfield.bf_xor(bf1, bf2)
not_result := bitfield.bf_not(bf1)
println(' AND: ${and_result.str()}')
println(' OR: ${or_result.str()}')
println(' XOR: ${xor_result.str()}')
println(' NOT: ${not_result.str()}')
}
Cli
This example demonstrates building structured CLI applications with commands, subcommands, and option flags in POSIX mode using the cli module.
module main
import cli
fn main() {
println('=== cli Module Demo ===')
mut app := cli.Command{
name: 'tool'
description: "A sample CLI tool showing V's cli package."
version: '1.0.0'
posix_mode: true
execute: fn (cmd cli.Command) ! {
println('Root command execution. Use --help to see subcommands.')
}
commands: [
cli.Command{
name: 'greet'
description: 'Greet a user with custom options'
posix_mode: true
execute: fn (cmd cli.Command) ! {
name := cmd.flags.get_string('name') or { 'Guest' }
verbose := cmd.flags.get_bool('verbose') or { false }
if verbose {
println('Log: Initiating greeting process...')
}
println('Hello, ${name}!')
}
flags: [
cli.Flag{
flag: .string
name: 'name'
abbrev: 'n'
description: 'Name of person to greet'
},
cli.Flag{
flag: .bool
name: 'verbose'
abbrev: 'v'
description: 'Enable verbose logging'
},
]
},
]
}
app.setup()
// Test by parsing args mock
println('\nParsing args: tool greet --name Antigravity -v')
app.parse(['tool', 'greet', '--name', 'Antigravity', '-v'])
}
Veb
This example demonstrates building a full-featured REST API with full CRUD operations (GET, POST, PUT, DELETE), clean helper functions for request parsing and validation (parse_and_validate_task), standardized JSON response functions (send_json, send_error, send_message), thread-safe state management (sync.RwMutex), and file-based JSON database persistence using the veb web framework.
module main
import json
import net.http
import os
import sync
import time
import veb
// ============================================================================
// 1. DATA MODEL & VALIDATION
// ============================================================================
// Task represents a item in our system stored in a JSON file.
struct Task {
mut:
id int @[json: 'id']
title string @[json: 'title']
details string @[json: 'details']
completed bool @[json: 'completed']
}
// validate checks that incoming task data meets our business rules.
fn (t Task) validate() ! {
if t.title.trim_space() == '' {
return error('Task title cannot be empty')
}
}
// ============================================================================
// 2. DATABASE LAYER (JSON FILE PERSISTENCE)
// ============================================================================
struct Database {
mut:
file_path string
tasks []Task
}
fn (mut db Database) load() ! {
if !os.exists(db.file_path) {
db.tasks = []Task{}
return
}
content := os.read_file(db.file_path)!
if content.trim_space() == '' {
db.tasks = []Task{}
return
}
db.tasks = json.decode([]Task, content)!
}
fn (mut db Database) save() ! {
encoded := json.encode_pretty(db.tasks)
os.write_file(db.file_path, encoded)!
}
// CRUD operations on the database
fn (db &Database) get_all() []Task {
return db.tasks
}
fn (db &Database) get_by_id(id int) ?Task {
for task in db.tasks {
if task.id == id {
return task
}
}
return none
}
fn (mut db Database) add(new_task Task) !Task {
new_task.validate()!
mut max_id := 0
for t in db.tasks {
if t.id > max_id {
max_id = t.id
}
}
created := Task{
id: max_id + 1
title: new_task.title.trim_space()
details: new_task.details.trim_space()
completed: new_task.completed
}
db.tasks << created
db.save()!
return created
}
fn (mut db Database) update(id int, update_data Task) !Task {
update_data.validate()!
for i in 0 .. db.tasks.len {
if db.tasks[i].id == id {
db.tasks[i].title = update_data.title.trim_space()
db.tasks[i].details = update_data.details.trim_space()
db.tasks[i].completed = update_data.completed
db.save()!
return db.tasks[i]
}
}
return error('Task with ID ${id} not found')
}
fn (mut db Database) delete(id int) ! {
for i, t in db.tasks {
if t.id == id {
db.tasks.delete(i)
db.save()!
return
}
}
return error('Task with ID ${id} not found')
}
// ============================================================================
// 3. HTTP APP & CONTEXT DEFINITION
// ============================================================================
struct App {
mut:
lock sync.RwMutex
db Database
}
pub struct Context {
veb.Context
}
// ============================================================================
// 4. REQUEST & RESPONSE HELPERS (ABSTRACTION LAYER)
// ============================================================================
// parse_and_validate_task parses JSON request body and validates field constraints.
fn parse_and_validate_task(mut ctx Context) !Task {
if ctx.req.data.trim_space() == '' {
return error('Request body cannot be empty')
}
task := json.decode(Task, ctx.req.data) or {
return error('Invalid JSON payload structure')
}
task.validate()!
return task
}
// send_json encodes data as JSON and sets the HTTP status code.
fn send_json[T](mut ctx Context, data T, status http.Status) veb.Result {
ctx.res.set_status(status)
return ctx.json(json.encode(data))
}
// send_error sets an HTTP error status code and returns a JSON error response.
fn send_error(mut ctx Context, message string, status http.Status) veb.Result {
ctx.res.set_status(status)
return ctx.json('{"error": "${message}"}')
}
// send_message sets an HTTP status code and returns a success JSON message.
fn send_message(mut ctx Context, message string, status http.Status) veb.Result {
ctx.res.set_status(status)
return ctx.json('{"message": "${message}"}')
}
// ============================================================================
// 5. ROUTE HANDLERS (CLEAN & CONCISE)
// ============================================================================
// GET / - Index welcome page
pub fn (app &App) index(mut ctx Context) veb.Result {
return ctx.text('Welcome to veb Clean CRUD API!')
}
// GET /api/tasks - Retrieve all tasks (READ)
@['/api/tasks'; get]
pub fn (mut app App) get_tasks(mut ctx Context) veb.Result {
app.lock.@rlock()
defer { app.lock.runlock() }
return send_json(mut ctx, app.db.get_all(), .ok)
}
// GET /api/tasks/:id - Retrieve a task by ID (READ)
@['/api/tasks/:id'; get]
pub fn (mut app App) get_task_by_id(mut ctx Context, id int) veb.Result {
app.lock.@rlock()
defer { app.lock.runlock() }
task := app.db.get_by_id(id) or {
return send_error(mut ctx, 'Task not found', .not_found)
}
return send_json(mut ctx, task, .ok)
}
// POST /api/tasks - Create a new task (CREATE)
@['/api/tasks'; post]
pub fn (mut app App) create_task(mut ctx Context) veb.Result {
payload := parse_and_validate_task(mut ctx) or {
return send_error(mut ctx, err.msg(), .bad_request)
}
app.lock.@lock()
defer { app.lock.unlock() }
created := app.db.add(payload) or {
return send_error(mut ctx, err.msg(), .internal_server_error)
}
return send_json(mut ctx, created, .created)
}
// PUT /api/tasks/:id - Update an existing task (UPDATE)
@['/api/tasks/:id'; put]
pub fn (mut app App) update_task(mut ctx Context, id int) veb.Result {
payload := parse_and_validate_task(mut ctx) or {
return send_error(mut ctx, err.msg(), .bad_request)
}
app.lock.@lock()
defer { app.lock.unlock() }
updated := app.db.update(id, payload) or {
status := if err.msg().contains('not found') { http.Status.not_found } else { http.Status.internal_server_error }
return send_error(mut ctx, err.msg(), status)
}
return send_json(mut ctx, updated, .ok)
}
// DELETE /api/tasks/:id - Delete a task by ID (DELETE)
@['/api/tasks/:id'; delete]
pub fn (mut app App) delete_task(mut ctx Context, id int) veb.Result {
app.lock.@lock()
defer { app.lock.unlock() }
app.db.delete(id) or {
return send_error(mut ctx, err.msg(), .not_found)
}
return send_message(mut ctx, 'Task deleted successfully', .ok)
}
// ============================================================================
// 6. MAIN DEMONSTRATION SUITE
// ============================================================================
fn main() {
println('=== veb Web Framework Full CRUD Demo (Clean Abstractions) ===')
db_file := 'veb_tasks_db.json'
defer {
if os.exists(db_file) {
os.rm(db_file) or {}
println('Cleaned up temporary database file: ${db_file}')
}
}
mut db := Database{ file_path: db_file }
db.load() or {}
mut app := &App{ db: db }
port := 30088
// Start server in background thread
spawn fn [mut app, port] () {
println('Starting veb server on http://localhost:${port}/...')
veb.run[App, Context](mut app, port)
}()
time.sleep(250 * time.millisecond)
base_url := 'http://localhost:${port}'
println('\n--- 1. POST /api/tasks (Validation Failure Test) ---')
invalid_json := '{"title": " ", "details": "No title provided"}'
resp_invalid := http.post_json('${base_url}/api/tasks', invalid_json) or { panic(err) }
println('Status: ${resp_invalid.status_code} | Body: ${resp_invalid.body}')
println('\n--- 2. POST /api/tasks (Create Task 1) ---')
valid_json1 := '{"title": "Learn V veb Abstractions", "details": "Clean helper functions for CRUD", "completed": false}'
resp_create1 := http.post_json('${base_url}/api/tasks', valid_json1) or { panic(err) }
println('Status: ${resp_create1.status_code} | Body: ${resp_create1.body}')
println('\n--- 3. POST /api/tasks (Create Task 2) ---')
valid_json2 := '{"title": "Build Clean REST API", "details": "Abstracted validation and responses", "completed": false}'
resp_create2 := http.post_json('${base_url}/api/tasks', valid_json2) or { panic(err) }
println('Status: ${resp_create2.status_code} | Body: ${resp_create2.body}')
println('\n--- 4. GET /api/tasks (List All Tasks) ---')
resp_all := http.get('${base_url}/api/tasks') or { panic(err) }
println('Status: ${resp_all.status_code} | Body:\n${resp_all.body}')
println('\n--- 5. GET /api/tasks/1 (Fetch Task by ID) ---')
resp_get := http.get('${base_url}/api/tasks/1') or { panic(err) }
println('Status: ${resp_get.status_code} | Body: ${resp_get.body}')
println('\n--- 6. PUT /api/tasks/1 (Update Task 1 to completed) ---')
update_json := '{"title": "Learn V veb Abstractions (Completed)", "details": "Clean helper functions for CRUD", "completed": true}'
resp_put := http.fetch(http.FetchConfig{
url: '${base_url}/api/tasks/1'
method: .put
header: http.new_header(http.HeaderConfig{ key: .content_type, value: 'application/json' })
data: update_json
}) or { panic(err) }
println('Status: ${resp_put.status_code} | Body: ${resp_put.body}')
println('\n--- 7. DELETE /api/tasks/2 (Delete Task 2) ---')
resp_del := http.fetch(http.FetchConfig{
url: '${base_url}/api/tasks/2'
method: .delete
}) or { panic(err) }
println('Status: ${resp_del.status_code} | Body: ${resp_del.body}')
println('\n--- 8. GET /api/tasks (Final Task List) ---')
resp_final := http.get('${base_url}/api/tasks') or { panic(err) }
println('Status: ${resp_final.status_code} | Body: ${resp_final.body}')
println('\n--- 9. Check JSON File Content on Disk ---')
if os.exists(db_file) {
db_content := os.read_file(db_file) or { '' }
println('JSON DB File (${db_file}) Content:\n${db_content}')
}
println('\nveb Full CRUD with clean abstractions completed successfully!')
}
Readline
This example demonstrates prompting users for text input from terminal lines in a structured manner using the readline module.
module main
import readline
fn main() {
println('=== readline Module Demo ===')
mut r := readline.Readline{}
println('Simulating readline input (feed via stdin if non-interactive):')
// Read a line from standard input
line := r.read_line('Enter text: ') or {
println('Error or EOF: ${err}')
return
}
println('You entered: "${line}"')
}
Runtime
This example demonstrates inspecting hardware specifications, processor cores, system endianness, and memory usage statistics using the runtime module.
module main
import runtime
fn main() {
println('=== runtime Module Demo ===')
// 1. CPU and job info
cpus := runtime.nr_cpus()
jobs := runtime.nr_jobs()
println('CPU Cores: ${cpus}')
println('Concurrent Jobs (VJOBS): ${jobs}')
// 2. System architecture details
println('Is 64-bit architecture? ${runtime.is_64bit()}')
println('Is 32-bit architecture? ${runtime.is_32bit()}')
println('Is Little Endian? ${runtime.is_little_endian()}')
println('Is Big Endian? ${runtime.is_big_endian()}')
// 3. Memory statistics
total_mem := runtime.total_memory() or { 0 }
free_mem := runtime.free_memory() or { 0 }
used_mem := runtime.used_memory() or { 0 }
// Format to megabytes
total_mb := total_mem / (1024 * 1024)
free_mb := free_mem / (1024 * 1024)
used_mb := used_mem / (1024 * 1024)
println('\nPhysical Memory info:')
println(' Total Memory: ${total_mb} MB')
println(' Free Memory: ${free_mb} MB')
println(' Used (by App): ${used_mb} MB')
}
Strings.Lorem Helper
Strings Lorem
V's standard library strings.lorem module provides a pseudo-random text generator based on a Markov chain built from embedded corpora. It produces structured text in the form of paragraphs and sentences, with options to control layouts, select specific corpora, and configure deterministic output.
This example demonstrates how to:
- Generate pseudo-random text with default configurations.
- Customize the output layout by adjusting words per sentence, sentences per paragraph, and paragraphs.
- Select specific corpora such as
lorem(Latin),poe(Edgar Allan Poe),darwin(Charles Darwin), andbard(William Shakespeare). - Run deterministic generation using an RNG seed and a custom starting phrase.
module main
import strings.lorem
fn main() {
println('=== V strings.lorem Standard Library Demo ===')
// 1. Basic Generation with Default Configuration
// By default, it will choose a random corpus, seed phrase, and RNG seed.
println('\n--- 1. Default Lorem Ipsum Generation ---')
default_lorem := lorem.generate(lorem.LoremCfg{})
println(default_lorem)
// 2. Custom Layout Configuration (Paragraphs, Sentences, Words)
println('\n--- 2. Custom Layout Generation ---')
custom_layout := lorem.generate(lorem.LoremCfg{
paragraphs: 2
sentences_per_paragraph: 3
words_per_sentence: 6
})
println(custom_layout)
// 3. Selection of Specific Embedded Corpora
// V's strings.lorem module supports four built-in corpora:
// - 'lorem' (Standard Latin Lorem Ipsum)
// - 'poe' (Edgar Allan Poe's The Raven)
// - 'darwin' (Charles Darwin's Origin of Species)
// - 'bard' (William Shakespeare's works)
println('\n--- 3. Specific Corpora Examples ---')
corpora := ['lorem', 'poe', 'darwin', 'bard']
for corpus in corpora {
text := lorem.generate(lorem.LoremCfg{
corpus_name: corpus
paragraphs: 1
sentences_per_paragraph: 2
words_per_sentence: 8
})
println('Corpus [${corpus}]:')
println(text)
println('-'.repeat(40))
}
// 4. Deterministic Text Generation using RNG Seed and Custom Seed Phrases
// Using a specific `rng_seed` guarantees that the generated pseudo-random text is deterministic
// and identical across multiple runs. Custom `seed_text` provides a starting phrase for the Markov chain.
println('\n--- 4. Deterministic Generation with Seed & Custom Starting Phrase ---')
deterministic_lorem_1 := lorem.generate(lorem.LoremCfg{
corpus_name: 'poe'
rng_seed: 42
seed_text: 'once upon a midnight'
paragraphs: 1
sentences_per_paragraph: 2
words_per_sentence: 8
})
deterministic_lorem_2 := lorem.generate(lorem.LoremCfg{
corpus_name: 'poe'
rng_seed: 42
seed_text: 'once upon a midnight'
paragraphs: 1
sentences_per_paragraph: 2
words_per_sentence: 8
})
println('Run 1:')
println(deterministic_lorem_1)
println('\nRun 2:')
println(deterministic_lorem_2)
// Assert that they are exactly identical due to deterministic seeding
assert deterministic_lorem_1 == deterministic_lorem_2
println('\nDeterministic assertion passed! Both runs generated identical text.')
}
WebAssembly Compilation
V has first-class support for WebAssembly (WASM). There are two distinct methods for compiling and working with WebAssembly in V:
- Compiling V source code to WASM (using direct/native backends or Emscripten).
- Programmatic WASM generation (using the built-in
wasminstruction-level builder).
Compiling V Source to WebAssembly
1. Native Direct Backend (`-b wasm`)
V contains a native compiler backend that bypasses C intermediate code and outputs WebAssembly binary files (.wasm) directly. This backend has zero dependencies and is extremely fast, though it is currently in active development and supports a subset of the language.
To compile a V file directly to WASM:
v -b wasm -o main.wasm main.v
Executing in JavaScript/Node.js:
To run the natively compiled WASM binary, instantiate it using the standard JavaScript WebAssembly API:
const fs = require("fs");
async function run() {
const wasmBuffer = fs.readFileSync("main.wasm");
const { instance } = await WebAssembly.instantiate(wasmBuffer, {
env: {
// Import host functions here if needed
},
});
// Call exported V functions from JS
console.log(instance.exports.add(5, 10));
}
run();
2. Emscripten C Backend (`-os wasm`)
For compiling complex applications, standard library features, or C-linked dependencies to WASM, V relies on the Emscripten toolchain. V translates the V code to intermediate C, and Emscripten compiles it to highly optimized WebAssembly.
Prerequisites:
Ensure the Emscripten SDK (emsdk) is installed and active on your PATH:
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
Compiling with V:
v -os wasm -o main.js main.v
This produces:
main.wasm: The compiled WebAssembly binary.main.js: Emscripten glue code to load the WASM module and map runtime environments (I/O, memory, filesystem).
Running in Node.js:
node main.js
Programmatic WASM Generation
Programmatic WASM Builder
V provides a built-in wasm module in its standard library that allows developers to programmatically build WebAssembly binary (.wasm) files directly using instruction-level builder patterns. This is extremely useful for compilers, runtime engines, or dynamic code generation targeting the browser and other WebAssembly runtimes.
This example demonstrates how to:
- Initialize a WebAssembly module (
wasm.Module). - Generate exported arithmetic functions (
add,sub,mul). - Construct and read mutable global variables (
new_global,global_get,global_set). - Build recursive control structures (a factorial
facfunction utilizingc_if,c_else,c_end, and recursivecall). - Compile the module down to a
.wasmbinary slice ([]u8) and save it to disk.
module main
import wasm
import os
fn main() {
println('=== V WebAssembly (wasm) Module Demo ===')
// Initialize the WebAssembly module
mut m := wasm.Module{}
m.enable_debug('vlang_wasm_demo')
// --- 1. Basic Arithmetic Functions ---
println('\n1. Generating Arithmetic Functions (add, sub, mul)...')
// Exported 'add' function: taking two i32 parameters, returning one i32 result
mut add_fn := m.new_function('add', [.i32_t, .i32_t], [.i32_t])
{
add_fn.local_get(0)
add_fn.local_get(1)
add_fn.add(.i32_t)
}
m.commit(add_fn, true) // commit with export = true
// Exported 'sub' function
mut sub_fn := m.new_function('sub', [.i32_t, .i32_t], [.i32_t])
{
sub_fn.local_get(0)
sub_fn.local_get(1)
sub_fn.sub(.i32_t)
}
m.commit(sub_fn, true)
// Exported 'mul' function
mut mul_fn := m.new_function('mul', [.i32_t, .i32_t], [.i32_t])
{
mul_fn.local_get(0)
mul_fn.local_get(1)
mul_fn.mul(.i32_t)
}
m.commit(mul_fn, true)
// --- 2. Global Variables ---
println('\n2. Creating Global Variables...')
// Global variable named '__vsp' (Stack Pointer), internal/non-exported, type i32, mutable, init value 10
vsp := m.new_global('__vsp', false, .i32_t, true, wasm.constexpr_value(10))
// Create a function that retrieves the global value, adds 20, stores it back, and returns the new value
mut vsp_fn := m.new_function('update_vsp', [], [.i32_t])
{
vsp_fn.global_get(vsp)
vsp_fn.i32_const(20)
vsp_fn.add(.i32_t)
vsp_fn.global_set(vsp)
vsp_fn.global_get(vsp)
}
m.commit(vsp_fn, true)
// --- 3. Recursive Functions (Factorial) ---
println('\n3. Generating Recursive Function (fac)...')
// fac(n) returns n! using i64 types
mut fac_fn := m.new_function('fac', [.i64_t], [.i64_t])
{
fac_fn.local_get(0)
fac_fn.eqz(.i64_t)
// If block: if n == 0, return 1
ifs := fac_fn.c_if([], [.i64_t])
{
fac_fn.i64_const(1)
}
fac_fn.c_else(ifs)
{
// Else: return n * fac(n - 1)
fac_fn.local_get(0) // push n
fac_fn.local_get(0)
fac_fn.i64_const(1)
fac_fn.sub(.i64_t) // n - 1
fac_fn.call('fac') // recursive call to fac(n - 1)
fac_fn.mul(.i64_t) // n * fac(n - 1)
}
fac_fn.c_end(ifs)
}
m.commit(fac_fn, true)
// --- 4. Compilation & Output ---
println('\n4. Compiling Module to WebAssembly Binary...')
binary_code := m.compile()
println('Compilation Successful! Binary size: ${binary_code.len} bytes')
// Save compiled binary as output.wasm in the current directory
dir := os.dir(@FILE)
output_path := os.join_path(dir, 'output.wasm')
os.write_file(output_path, binary_code.bytestr()) or {
println('Failed to write output.wasm: ${err}')
return
}
println('Saved Wasm binary to: ${output_path}')
}
sizeof and \_\_offsetof
sizeof and \_\_offsetof
V provides two built-in operators for determining sizes and memory offsets:
sizeof(Type): Returns the memory size of the given Type in bytes.__offsetof(Struct, field_name): Returns the offset in bytes of a field relative to the start of the struct.
Step-by-Step Code Walkthrough:
- Size of
Point:
sizeof(Point) yields 8 bytes because it has two int fields (4 bytes each).
- Size of
Foo& Alignment Padding:
sizeof(Foo) yields 12 bytes, even though it contains a int (4 bytes), b u8 (1 byte), and c int (4 bytes). This happens because of C ABI alignment: V aligns struct members on word boundaries (in this case, 4 bytes), adding 3 bytes of invisible padding after b u8.
- Offsets of fields in
Foo: __offsetof(Foo, a)yields0because it starts at byte 0.__offsetof(Foo, b)yields4because it starts right aftera(4 bytes).__offsetof(Foo, c)yields8because it aligns on the next 4-byte boundary due to padding afterb.
Additional Context from Repository docs:
This example demonstrates the concepts of sizeof and \\offsetof.
module main
struct Point {
x int
y int
}
struct Foo {
a int
b u8
c int
}
fn main() {
// sizeof gives the size of a type in bytes
println('sizeof(Point) = ${sizeof(Point)} bytes') // 8
println('sizeof(Foo) = ${sizeof(Foo)} bytes') // 12 (due to alignment/padding in C backend)
// __offsetof gives the offset in bytes of a struct field
println('__offsetof(Point, x) = ${__offsetof(Point, x)}') // 0
println('__offsetof(Point, y) = ${__offsetof(Point, y)}') // 4
println('__offsetof(Foo, a) = ${__offsetof(Foo, a)}') // 0
println('__offsetof(Foo, b) = ${__offsetof(Foo, b)}') // 4
println('__offsetof(Foo, c) = ${__offsetof(Foo, c)}') // 8
}
Limited Operator Overloading
Limited Operator Overloading
Operator overloading is supported for a limited set of binary operators (+, -, *, **, /, %, <, ==) to improve readability in scientific, mathematical, and graphics applications. V does not support indexing ([]) or assignment (=) overloading. Overloading the + operator automatically synthesizes the matching assignment operator (e.g., +=).
Step-by-Step Code Walkthrough:
- Binary Operator Overloading:
We define the + and - operators for struct Vec using the syntax fn (a Vec) + (b Vec) Vec. V invokes these methods directly when performing arithmetic expressions like a + b or a - b.
- Equality Operator Overloading:
We define == to verify element-by-element equality: a.x == b.x && a.y == b.y.
- Automatic Assignment Generation:
When we overload +, V autogenerates the compound assignment operator +=. Executing c += a resolves to c = c + a, compiling and modifying c automatically.
Additional Context from Repository docs:
This example demonstrates the concepts of limited operator overloading.
module main
struct Vec {
x int
y int
}
fn (a Vec) str() string {
return '{${a.x}, ${a.y}}'
}
// Overload addition operator (+)
fn (a Vec) + (b Vec) Vec {
return Vec{a.x + b.x, a.y + b.y}
}
// Overload subtraction operator (-)
fn (a Vec) - (b Vec) Vec {
return Vec{a.x - b.x, a.y - b.y}
}
// Overload equality operator (==)
fn (a Vec) == (b Vec) bool {
return a.x == b.x && a.y == b.y
}
fn main() {
a := Vec{2, 3}
b := Vec{4, 5}
mut c := Vec{1, 2}
println('a + b = ${a + b}') // {6, 8}
println('a - b = ${a - b}') // {-2, -2}
// c += a is autogenerated from the + overload!
c += a
println('c after += a: ${c}') // {3, 5}
println('a == b: ${a == b}') // false
println('a == Vec{2, 3}: ${a == Vec{2, 3}}') // true
}
Atomics
Atomics
V does not have direct keyword support for atomic operations but integrates standard C11 atomic capabilities through platform-specific wrapper headers (found under stdatomic/). Variables can be treated atomically by executing type-specific functions prefixed by C.atomic_ and passing references to the target variables.
Step-by-Step Code Walkthrough:
- C Header Inclusion:
Depending on the compilation target, V includes either the Unix or Windows wrapper headers atomic.h containing atomic operations.
- C declarations:
fn C.atomic_store_u32(&u32, u32) and other methods are declared. Since V does not automatically parse C headers, we manually declare the C functions we want to invoke.
- Local Storage:
We declare a local variable atom := u32(0) inside main(). Any variable can be treated atomically by passing its memory reference (pointer) to the C functions. This avoids needing global variables or compiling with -enable-globals flag.
- CAS (Compare-And-Swap):
Inside unsafe { ... }, we call C.atomic_compare_exchange_strong_u32(&atom, &expected, 23). If the value at &atom matches expected (17), it replaces it with 23 and returns true. If not, it loads the actual current value into expected and returns false.
Additional Context from Repository docs:
This example demonstrates the concepts of atomics.
module main
$if windows {
#include "@VEXEROOT/thirdparty/stdatomic/win/atomic.h"
} $else {
#include "@VEXEROOT/thirdparty/stdatomic/nix/atomic.h"
}
// declare the C functions we want to use
fn C.atomic_store_u32(&u32, u32)
fn C.atomic_load_u32(&u32) u32
fn C.atomic_compare_exchange_strong_u32(&u32, &u32, u32) bool
fn main() {
// Ordinary local variable, treated as atomic by passing its reference
mut atom := u32(0)
// Initialize atomic variable
unsafe {
C.atomic_store_u32(&atom, 17)
mut expected := u32(17)
// Atomic CAS: if atom == expected, set atom to 23 and return true
if C.atomic_compare_exchange_strong_u32(&atom, &expected, 23) {
println('Exchange successful, atom is now 23')
} else {
println('Exchange failed, atom is ${C.atomic_load_u32(&atom)}')
}
println('Final value: ${C.atomic_load_u32(&atom)}')
}
}
Static Variables
Static Variables
V supports Static Variables within functions. They behave like namespaced global variables, preserving state across function calls, but are restricted to the local scope of a single function. Functions containing static variables must be marked with @[unsafe] and calls must occur in unsafe blocks. They are primarily intended for facilitating low-level C code translations.
Step-by-Step Code Walkthrough:
- Static Variable Definition:
Inside the function counter(), we define mut static x := 42. The compiler generates this variable in the global data space but restricts access to it exclusively within counter().
- One-Time Initialization:
The initialization expression = 42 is executed exactly once when the program starts. It is skipped on subsequent calls to counter().
- Access Rules:
Because static variables represent shared mutable state, counter() is marked with @[unsafe] and called within unsafe blocks in main().
Additional Context from Repository docs:
This example demonstrates the concepts of static variables.
module main
// V supports function-scoped static variables inside unsafe functions
@[unsafe]
fn counter() int {
// static variables are initialized only once
mut static x := 42
x++
return x
}
fn main() {
println(unsafe { counter() }) // 43
println(unsafe { counter() }) // 44
println(unsafe { counter() }) // 45
}
Hot Code Reloading
Hot Code Reloading
V supports Hot Code Reloading using the @[live] attribute. By compiling a program with the -live flag (e.g. v -live run file.v), V monitors the source files, automatically recompiles live-annotated functions to a shared library, and reloads them dynamically at runtime without restarting the application.
Step-by-Step Code Walkthrough:
- Live Annotation:
The function print_message() is marked with the @[live] attribute. This instructs the compiler to generate it as a hot-reloadable hook loading from a shared library.
- Main loop execution:
The loop in main() runs continuously. If you modify the message in the string print inside print_message() and save the file, V's monitoring thread detects the change, rebuilds the code, and subsequent calls in the loop print the updated message instantly.
Additional Context from Repository docs:
This example demonstrates the concepts of hot code reloading.
module main
import time
// Functions that should be reloaded must have `@[live]` attribute
@[live]
fn print_message() {
println('Hello! Modify this message while the program is running under -live mode.')
}
fn main() {
// A simple loop printing the message
for i in 0 .. 3 {
print_message()
time.sleep(100 * time.millisecond)
}
}
Compile-Time Reflection
Compile-Time Reflection
V supports Compile-Time Reflection using the $ prefix to perform type operations, evaluations, and code generation during compilation. V iterates over struct fields, attributes, variants, and methods at compile-time using $for loops, providing static safety without any runtime metadata overhead.
- Struct fields can be examined via
Struct.fields. - Struct attributes can be examined via
Struct.attributes. - Struct methods can be examined via
Struct.methods, and executed dynamically using$method().
Step-by-Step Code Walkthrough:
- Field Iteration (
$for field in User.fields):
Iterates over all fields of User. Evaluates their names (field.name) and type IDs (field.typ) at compile-time.
- Attribute Iteration (
$for attr in User.attributes):
Inspects attributes attached to the struct, such as @[COLOR] (prints COLOR).
- Comptime Method Invocation (
user.$method()):
Inside $for m in User.methods, we check if the method returns a string. If so, we execute the method dynamically using the $method() comptime syntax, calling greet() on the user instance and printing Hello Alice.
Additional Context from Repository docs:
This example demonstrates the concepts of compile-time reflection.
module main
@[COLOR]
struct User {
name string
age int
}
fn (u User) greet() string {
return 'Hello ${u.name}'
}
fn main() {
println('--- Struct Fields Reflection ---')
$for field in User.fields {
println('Field: ${field.name} | Typ: ${field.typ}')
}
println('\n--- Struct Attributes Reflection ---')
$for attr in User.attributes {
println('Attribute name: ${attr.name}')
}
println('\n--- Struct Methods Reflection ---')
user := User{
name: 'Alice'
age: 30
}
$for m in User.methods {
$if m.return_type is string {
println(user.$method())
}
}
}
Compile-Time Pseudo Variables
Compile-Time Pseudo Variables
V provides a set of pseudo-variables starting with @ that are evaluated and substituted at compile time:
- Scope Identifiers:
@FN-> Replaced with the name of the current V function (as a string).@METHOD-> Replaced with theReceiverType.MethodNameof the current method (as a string).@MOD-> Replaced with the name of the current V module (as a string).@STRUCT-> Replaced with the name of the current V struct (as a string).
- Source File & Location Identifiers:
@FILE-> Replaced with the absolute path of the V source file (as a string).@DIR-> Replaced with the absolute path of the folder containing the V source file (as a string).@LINE-> Replaced with the V line number where it appears (as a string).@FILE_LINE-> Like@FILE:@LINE, but the file part is a relative path (as a string).@LOCATION-> Combines file, line, and type/method name; suitable for logging.@COLUMN-> Replaced with the 1-based column offset where it appears (as a string).
- V Compiler & Git Identifiers:
@VEXE-> Replaced with the path to the V compiler executable (as a string).@VEXEROOT-> Replaced with the folder containing the V compiler executable (as a string).@VHASH-> Replaced with the shortened commit hash of the V compiler (as a string).@VCURRENTHASH-> Similar to@VHASH, but updates when the compiler is recompiled after local modifications or git bisect.
- Project Mod Info (requires
v.modin project root): @VMOD_FILE-> Replaced with the contents of the nearestv.modfile (as a string).@VMODHASH-> Replaced with the shortened commit hash derived from the.gitdirectory next to the nearestv.modfile (as a string).@VMODROOT-> Replaced with the path to the directory containing the nearestv.modfile (as a string).
- Build Time Identifiers (UTC timezone):
@BUILD_DATE-> Replaced with the build date (e.g.'2026-06-26').@BUILD_TIME-> Replaced with the UTC build time (e.g.'17:50:24').@BUILD_TIMESTAMP-> Replaced with the Unix timestamp of the build (e.g.'1782496224').- Note: Build variables can be overridden by setting the
SOURCE_DATE_EPOCHenvironment variable, enabling reproducible builds (e.g., setting it to the latest git commit timestamp).
- Target Platform / Toolchain Identifiers:
@OS-> Replaced with the OS type (e.g.'macos','linux','windows').@CCOMPILER-> Replaced with the C compiler used (e.g.'gcc','clang').@BACKEND-> Replaced with the current language backend (e.g.'c','js').@PLATFORM-> Replaced with the CPU architecture type (e.g.'arm64','amd64').
Additional Context from Repository docs:
This example demonstrates the usage and output of all the available compile-time pseudo variables in V.
module main
struct User {
name string
}
fn (u User) register() {
println('Executing method: ' + @METHOD) // User.register
println('Defined in struct: ' + @STRUCT) // User
}
fn log_event() {
println('Logging from function: ' + @FN) // log_event
}
fn main() {
println('=== V Compile-Time Pseudo Variables ===')
// Module & File Info
println('Current Module: ' + @MOD)
println('Source File Path: ' + @FILE)
println('Source Directory: ' + @DIR)
println('Line Number: ' + @LINE.str())
println('Relative File/Line: ' + @FILE_LINE)
println('Log Location: ' + @LOCATION)
println('Column Number: ' + @COLUMN.str())
// Compiler Info
println('V Compiler Executable: ' + @VEXE)
println('V compiler Root Directory: ' + @VEXEROOT)
println('V Compiler Commit Hash: ' + @VHASH)
println('V Compiler Current Hash: ' + @VCURRENTHASH)
// project Info (from v.mod)
println('v.mod File Contents: ' + @VMOD_FILE)
println('v.mod Git Commit Hash: ' + @VMODHASH)
println('v.mod Root Directory: ' + @VMODROOT)
// Build Info (UTC timezone)
println('Build Date: ' + @BUILD_DATE)
println('Build Time: ' + @BUILD_TIME)
println('Build Timestamp: ' + @BUILD_TIMESTAMP)
// Platform / Backend Info
println('Target OS: ' + @OS)
println('C Compiler: ' + @CCOMPILER)
println('V Backend: ' + @BACKEND)
println('CPU Platform: ' + @PLATFORM)
u := User{
name: 'Alice'
}
u.register()
log_event()
}
Environment-Specific Files & Compile-Time Types
Environment-Specific Files & Compile-Time Types
Environment-Specific Files
V supports compile-time filtering of entire files using file suffix conventions instead of conditional directives:
.js.v-> compiled only when targeting the Javascript backend..c.v-> compiled only when targeting the C backend._nix.c.v-> compiled only on Unix-like platforms._windows.c.v-> compiled only on Windows._d_customflag.v-> compiled only if-d customflagis passed to the compiler.
Compile-Time Types
When writing generic code, V provides specialized compile-time type matching identifiers to selectively check generic constraints:
$alias-> matches type aliases.$array-> matches arrays and fixed-size arrays.$enum-> matches enum types.$float/$int/$string-> matches floating numbers, integers, or string values.$struct-> matches struct types.
References & Pointers
References & Pointers
In V, references are similar to pointers in Go/C and references in C++. They allow you to point to a memory location of another variable without making a copy of its contents.
1. Passing by Value vs. Passing by Reference
When passing immutable arguments (like structs) to functions or methods, V decides under the hood whether to pass them by value or by reference depending on performance characteristics. As a developer, you do not need to worry about this optimization detail.
However, you can explicitly force an argument or method receiver to be passed by reference by prefixing the type with & (e.g., &Foo).
2. Mutability of References
References in V are immutable by default:
- Even if a function or method receives a reference (
&Foo), it cannot modify the fields of that struct unless the parameter is marked as mutable. - To allow modification, the argument must be declared as
mut foo Foo(which V automatically passes by reference under the hood) and called withmut(e.g.modify_foo(mut my_foo)). Note that modifiable fields must also be defined under themut:access block in the struct declaration.
3. Dereferencing
To access the underlying value of a reference directly, or to create a copy of the pointed-to object, use the dereferencing operator * (e.g., copied_foo := *ref_to_foo), similar to Go and C.
4. Recursive Structures
Recursive data structures (such as linked lists or trees) require fields that reference their own type. Because V needs to calculate the memory size of structs at compile time, recursive fields must be declared as references (e.g., left ?&Node[T]) because references have a fixed pointer size.
- To allow optional/empty references (like leaf node terminations in trees), V supports optional references (e.g.
?&Node[T]). These are automatically initialized tononeby default, avoiding the need forunsafe { nil }pointers or dummy nodes.
Here is the complete demonstration program showcasing V references and recursive struct designs:
module main
struct Foo {
mut:
abc int
}
// 1. A method receiving a reference. The receiver type is &Foo.
// Even though it is a reference, `foo` is immutable and cannot be changed here.
fn (foo &Foo) print_abc() {
println('print_abc: foo.abc = ${foo.abc}')
}
// 2. A regular function receiving a reference to Foo.
fn show_foo(foo &Foo) {
println('show_foo: foo.abc = ${foo.abc}')
}
// 3. To modify a reference, we must pass it as mutable.
// Note that mutable parameters are passed by reference under the hood.
fn modify_foo(mut foo Foo, new_val int) {
foo.abc = new_val
}
// 4. References are crucial for recursive types (like trees or linked lists).
// Since the size of Node must be known at compile time, recursive fields must be references.
// To allow optional/empty references (like leaf node terminations), V uses optional references (?&Node[T]).
struct Node[T] {
val T
left ?&Node[T]
right ?&Node[T]
}
fn main() {
println('=== V References & Pointers Demo ===')
// Creating a struct instance
mut my_foo := Foo{
abc: 100
}
// Calling method on reference. V automatically takes the address of my_foo.
my_foo.print_abc()
// Calling a function expecting a reference using & operator.
show_foo(&my_foo)
// Modifying the struct via a mutable receiver/argument.
modify_foo(mut my_foo, 200)
println('After modify_foo: my_foo.abc = ${my_foo.abc}')
// 5. Dereferencing a reference using the `*` operator.
ref_to_foo := &my_foo
// To copy the value of the struct pointed to by ref_to_foo:
copied_foo := *ref_to_foo
println('Copied foo abc: ${copied_foo.abc}')
// 6. Generic Tree structure using optional references
// Optional references are auto-initialized to `none`, so we don't need dummy nodes or `unsafe` blocks.
left_leaf := Node[int]{
val: 5
}
right_leaf := Node[int]{
val: 15
}
// Create root node pointing to leaf references
root := Node[int]{
val: 10
left: &left_leaf
right: &right_leaf
}
println('Root val: ${root.val}')
// Access the optional child nodes safely using if guards
if left := root.left {
println('Left leaf val: ${left.val}')
}
if right := root.right {
println('Right leaf val: ${right.val}')
}
}
Dumping Expressions at Runtime
Dumping Expressions at Runtime
You can dump/trace the value of any V expression using dump(expr). For example, save this code sample as factorial.v, then run it with v run factorial.v:
module main
fn factorial(n u32) u32 {
if dump(n <= 1) {
return dump(1)
}
return dump(n * factorial(n - 1))
}
fn main() {
println(factorial(5))
}
You will get:
[factorial.v:4] n <= 1: false
[factorial.v:4] n <= 1: false
[factorial.v:4] n <= 1: false
[factorial.v:4] n <= 1: false
[factorial.v:4] n <= 1: true
[factorial.v:5] 1: 1
[factorial.v:7] n * factorial(n - 1): 2
[factorial.v:7] n * factorial(n - 1): 6
[factorial.v:7] n * factorial(n - 1): 24
[factorial.v:7] n * factorial(n - 1): 120
120
Chapter 14 Useful Boilerplates and Application Templates
Quick Access
Below is an index of all code examples in this chapter. You can use these links to jump directly to any specific code example:
Boilerplate Templates
- CLI Command-Line Application Boilerplate
- REST API Server Boilerplate
- Worker Pool Concurrency Boilerplate
- OS and File Utilities Boilerplate
- String Utilities Boilerplate
- Math and Statistics Boilerplate
- Array Utilities Boilerplate
- Configuration Management Boilerplate
- JSON File Store Boilerplate
- Retry and Backoff Boilerplate
- HTTP Client Boilerplate
- CSV Processor Boilerplate
- macOS Native GUI Boilerplate
- Logging Boilerplate
This chapter provides a collection of production-ready, fully commented boilerplate templates designed to jumpstart your development with V. These examples showcase common application patterns, standard library usage, and best practices.
Boilerplate Templates
CLI Command-Line Application Boilerplate
CLI Command-Line Application Boilerplate
When building command-line utilities, V provides a highly featured standard flag module. Rather than manually parsing arguments from os.args, the flag module simplifies declaring options, validation, and auto-generates helpful usage/help text.
Key concepts illustrated:
- Initializing flag parser: Creating a new parser with
flag.new_flag_parser(os.args). - Defining Flags: Specifying flag names, short character abbreviations, default values, and description text.
- Config Struct Pattern: Cleanly separating parsed options from application logic using a custom config struct.
- Error Handling & Finalization: Handling invalid command usage gracefully using
fp.finalize().
module main
import flag
import os
// Config holds the validated configuration settings for the application.
struct Config {
input_file string
output_file string
verbose bool
retries int
mode string
}
fn main() {
// 1. Initialize the flag parser with command-line arguments (os.args)
mut fp := flag.new_flag_parser(os.args)
fp.application('v-cli-boilerplate')
fp.version('1.0.0')
fp.description('A professional CLI boilerplate showing flag parsing, validation, and structured configurations in V.')
// 2. Skip the executable path during parsing
fp.skip_executable()
// 3. Define flags with short abbreviations, default values, and descriptions
input_file := fp.string('input', `i`, '', 'Path to the input file (required)')
output_file := fp.string('output', `o`, 'output.txt', 'Path to the output file')
verbose := fp.bool('verbose', `v`, false, 'Enable verbose logging')
retries := fp.int('retries', `r`, 3, 'Number of retries for operation')
mode := fp.string('mode', `m`, 'default', 'Operation mode (default, fast, safe)')
// 4. Finalize parsing. This returns remaining non-flag arguments or an error.
additional_args := fp.finalize() or {
eprintln('Error: ${err}')
println(fp.usage())
exit(1)
}
// 5. Validate required flags and values
if input_file == '' {
eprintln('Error: --input (-i) flag is required.')
println(fp.usage())
exit(1)
}
// Validate allowed values for a string enum
if mode !in ['default', 'fast', 'safe'] {
eprintln('Error: Invalid mode "${mode}". Must be one of: default, fast, safe.')
println(fp.usage())
exit(1)
}
// 6. Map parsed arguments to the Config struct for clean division of concerns
config := Config{
input_file: input_file
output_file: output_file
verbose: verbose
retries: retries
mode: mode
}
// 7. Run the application logic
run_app(config, additional_args)
}
fn run_app(cfg Config, args []string) {
if cfg.verbose {
println('[DEBUG] Starting application execution...')
println('[DEBUG] Config: ${cfg}')
if args.len > 0 {
println('[DEBUG] Positional Arguments: ${args}')
}
}
println('Processing input file: ${cfg.input_file}')
println('Operation mode: ${cfg.mode}')
println('Retries configured: ${cfg.retries}')
// Perform work here...
println('Writing results to output file: ${cfg.output_file}')
println('Success: Application executed successfully!')
}
REST API Server Boilerplate
REST API Server Boilerplate
V's standard web framework, veb, is optimized for building fast, high-conformance web apps and APIs. This template serves as a quick-start scaffolding for a JSON REST service, illustrating CRUD routing and request decoding.
Key concepts illustrated:
- Routing Attributes: Tagging methods with route paths and HTTP verbs (e.g.
@['/api/items'; get]). - Path Parameters: Defining routes with dynamic segments like
@['/api/items/:id'; get]which map directly to method arguments. - JSON Serialization/Deserialization: Using
json.encodeandjson.decodeto work with HTTP requests and responses. - State Management & Thread Safety: Using fields in the global
Appstruct to share resources, protected by async.RwMutexto ensure thread-safe concurrent access.
module main
import json
import os
import sync
import veb
// Item represents a data model in our API.
struct Item {
id int @[json: 'id']
name string @[json: 'name']
done bool @[json: 'done']
}
// App holds the global state of the application.
struct App {
mut:
lock sync.RwMutex
items []Item
}
// Context wraps veb's request/response lifecycle.
struct Context {
veb.Context
}
// 1. GET / - Simple text response index endpoint
fn (mut app App) index(mut ctx Context) veb.Result {
return ctx.text('Welcome to the V REST API Boilerplate! Use /api/items to interact with the service.')
}
// 2. GET /api/items - Returns list of all items as JSON
@['/api/items'; get]
fn (mut app App) get_items(mut ctx Context) veb.Result {
app.lock.@rlock()
defer { app.lock.runlock() }
return ctx.json(json.encode(app.items))
}
// 3. GET /api/items/:id - Returns a single item by id, or 404
@['/api/items/:id'; get]
fn (mut app App) get_item(mut ctx Context, id int) veb.Result {
app.lock.@rlock()
defer { app.lock.runlock() }
for item in app.items {
if item.id == id {
return ctx.json(json.encode(item))
}
}
ctx.res.set_status(.not_found)
return ctx.json('{"error": "Item not found"}')
}
// 4. POST /api/items - Decodes JSON request body and adds a new item
@['/api/items'; post]
fn (mut app App) create_item(mut ctx Context) veb.Result {
new_item := json.decode(Item, ctx.req.data) or {
ctx.res.set_status(.bad_request)
return ctx.json('{"error": "Invalid JSON format"}')
}
app.lock.@lock()
defer { app.lock.unlock() }
// Auto-increment ID based on length
item_to_add := Item{
id: app.items.len + 1
name: new_item.name
done: new_item.done
}
app.items << item_to_add
ctx.res.set_status(.created)
return ctx.json(json.encode(item_to_add))
}
fn main() {
// Initialize App with mock seed data
mut app := &App{
items: [
Item{
id: 1
name: 'Learn V syntax'
done: true
},
Item{
id: 2
name: 'Build a REST API in V'
done: false
},
]
}
// Read port from environment variable or default to 8082 to avoid common conflicts on 8080
port_env := os.getenv('PORT')
port := if port_env != '' { port_env.int() } else { 8082 }
println('Starting REST API server on http://localhost:${port}...')
// Start veb web server
veb.run[App, Context](mut app, port)
}
Worker Pool Concurrency Boilerplate
Worker Pool Concurrency Boilerplate
V offers lightweight concurrency out of the box. By combining spawn (thread creation) with typed channels (chan), you can build thread-safe worker pools that process computationally intensive or high-latency tasks in parallel without lock overhead.
Key concepts illustrated:
- Channel Communication: Sending and receiving tasks and results over thread-safe queues.
- Spawned Threads: Running worker functions concurrently using the
spawnkeyword. - Synchronization with WaitGroups: Using
sync.WaitGroupto track and coordinate concurrent worker completion. - Monitor Thread Pattern: Spawning a monitor thread to wait for workers to finish and close the results channel, avoiding channel deadlocks.
- Graceful Shutdown: Closing the tasks channel (
tasks_chan.close()) to signal worker threads to cleanly exit.
module main
import time
import sync
// Task represents the unit of work to be processed.
struct Task {
id int
data string
}
// Result represents the outcome of processing a Task.
struct Result {
task_id int
worker_id int
output string
duration time.Duration
}
// worker runs in a separate thread, consuming from tasks_chan and producing to results_chan.
fn worker(id int, tasks_chan chan Task, results_chan chan Result, mut wg sync.WaitGroup) {
defer {
wg.done()
}
for {
// Receive a task from the channel.
// If the channel is closed and empty, it returns `none`
t := <-tasks_chan or { break }
start_time := time.now()
// Simulate intensive processing/I/O task
time.sleep(50 * time.millisecond)
elapsed := time.since(start_time)
// Send the result to the output channel
results_chan <- Result{
task_id: t.id
worker_id: id
output: 'Processed: ' + t.data.to_upper()
duration: elapsed
}
}
}
// wait_and_close waits for all workers to finish and then closes the results channel.
fn wait_and_close(mut wg sync.WaitGroup, results_chan chan Result) {
wg.wait()
results_chan.close()
}
fn main() {
println('=== V Worker Pool Concurrency Boilerplate ===')
// 1. Create channels for tasks and results with capacities
tasks_chan := chan Task{cap: 10}
results_chan := chan Result{cap: 10}
num_workers := 3
num_tasks := 5
mut wg := sync.new_waitgroup()
// 2. Spawn concurrent worker threads
println('Spawning ${num_workers} workers...')
for i in 0 .. num_workers {
wg.add(1)
spawn worker(i + 1, tasks_chan, results_chan, mut wg)
}
// Spawn the monitor thread to close results_chan when all workers are done
spawn wait_and_close(mut wg, results_chan)
// 3. Dispatch tasks to the queue
println('Dispatching ${num_tasks} tasks to worker pool...')
for i in 0 .. num_tasks {
tasks_chan <- Task{
id: i + 1
data: 'task-payload-${i + 1}'
}
}
// 4. Close tasks channel to signal workers that no more work is coming
tasks_chan.close()
println('Tasks dispatched, queue closed. Collecting results...')
// 5. Collect results from results channel by iterating until it is closed
mut results := []Result{}
for {
res := <-results_chan or { break }
results << res
println('Received: Task #${res.task_id} from Worker #${res.worker_id} (took ${res.duration.milliseconds()}ms)')
}
// 6. Print summary
println('\n=== Processing Summary ===')
for res in results {
println('- Task #${res.task_id} -> ${res.output} (Worker #${res.worker_id})')
}
}
OS and File Utilities Boilerplate
OS and File Utilities Boilerplate
V's standard os module contains comprehensive and platform-agnostic tools for interacting with the file system and host operating system.
Key concepts illustrated:
- Path Manipulation: Using
os.join_pathto build paths correctly across Linux, macOS, and Windows. - File I/O: Performing quick reads and writes using
os.read_fileandos.write_file. - Directory Operations: Checking folder existence, recursively creating folders via
os.mkdir_all, and listing files viaos.ls. - Metadata Inspection: Interrogating file properties like sizes and types (
os.is_file/os.is_dir). - Resource Cleanup: Ensuring system hygiene by safely removing files (
os.rm) and folders (os.rmdir).
module main
import os
fn main() {
println('=== V OS & File Utilities Boilerplate ===')
// 1. Join paths safely across different operating systems using os.join_path
// V's home_dir() gives the user's home directory. Let's use it as a base path inside a temporary subfolder in workspace.
cwd := os.getwd()
temp_dir := os.join_path(cwd, 'temp_file_demo')
println('Target directory: ${temp_dir}')
// 2. Check if a directory exists, and create it recursively if not
if !os.exists(temp_dir) {
println('Directory does not exist. Creating...')
os.mkdir_all(temp_dir) or {
eprintln('Failed to create directory: ${err}')
exit(1)
}
}
target_file := os.join_path(temp_dir, 'sample.txt')
println('Target file path: ${target_file}')
// 3. Write data to a file (overwrites if it already exists)
content_to_write := 'Hello V Developers!\nThis is a sample file created by the OS and File utilities boilerplate.'
os.write_file(target_file, content_to_write) or {
eprintln('Failed to write to file: ${err}')
exit(1)
}
println('File written successfully.')
// 4. Read data from a file back into memory
read_content := os.read_file(target_file) or {
eprintln('Failed to read file: ${err}')
exit(1)
}
println('\n--- Read Content ---')
println(read_content)
println('--------------------\n')
// 5. Query file metadata
size := os.file_size(target_file)
is_file := os.is_file(target_file)
is_dir := os.is_dir(temp_dir)
println('File properties:')
println('- Size: ${size} bytes')
println('- Is File: ${is_file}')
println('- Is Directory: ${is_dir}')
// 6. List all files and folders inside a directory
println('\nListing contents of directory: ${temp_dir}')
files := os.ls(temp_dir) or { []string{} }
for file in files {
full_path := os.join_path(temp_dir, file)
file_type := if os.is_dir(full_path) { '[DIR]' } else { '[FILE]' }
println(' ${file_type} ${file}')
}
// 7. Clean up by deleting the file and directory
println('\nCleaning up temporary files and directories...')
os.rm(target_file) or { eprintln('Failed to delete file: ${err}') }
os.rmdir(temp_dir) or { eprintln('Failed to delete directory: ${err}') }
println('Cleanup completed successfully.')
}
String Utilities Boilerplate
String Utilities Boilerplate
This boilerplate demonstrates how to implement highly useful custom string operations that are not natively built into V's core string type. It highlights UTF-8 rune handling, string tokenization, filtering, and text manipulation.
Key concepts illustrated:
- UTF-8 Safe String Reversal: Reversing strings correctly by iterating over
runesrather than raw bytes, ensuring multi-byte Unicode characters (like emojis) are not corrupted. - Title Casing: Capitalizing the first letter of each word in a sentence (V's native
.capitalize()only capitalizes the first letter of the entire string). - Alphanumeric Palindrome Checking: Checking if a string is a palindrome while ignoring non-alphanumeric symbols and character casing.
- Rune-based Truncation: Cutting off a string at a specific character limit and adding an ellipsis, avoiding splitting UTF-8 byte sequences.
- Slugification: Generating clean, URL-friendly slugs (e.g. low-cased, hyphen-separated, special characters stripped) from user-supplied titles.
module main
// reverse_string reverses a string, properly handling multi-byte UTF-8 characters (runes).
fn reverse_string(s string) string {
runes := s.runes()
mut rev_runes := []rune{cap: runes.len}
for i := runes.len - 1; i >= 0; i-- {
rev_runes << runes[i]
}
return rev_runes.string()
}
// title_case capitalizes the first letter of each word in a string.
fn title_case(s string) string {
words := s.split(' ')
mut titled_words := []string{cap: words.len}
for word in words {
if word.len == 0 {
titled_words << ''
continue
}
titled_words << word.capitalize()
}
return titled_words.join(' ')
}
// is_palindrome checks if a string reads the same forwards and backwards,
// ignoring case and non-alphanumeric characters.
fn is_palindrome(s string) bool {
// Filter to lowercase alphanumeric characters
mut clean_chars := []rune{}
for r in s.to_lower().runes() {
if (r >= `a` && r <= `z`) || (r >= `0` && r <= `9`) {
clean_chars << r
}
}
for i in 0 .. clean_chars.len / 2 {
if clean_chars[i] != clean_chars[clean_chars.len - 1 - i] {
return false
}
}
return true
}
// truncate cuts off a string at a specified limit (by rune count) and appends an ellipsis.
fn truncate(s string, limit int) string {
runes := s.runes()
if runes.len <= limit {
return s
}
return runes[0..limit].string() + '...'
}
// slugify converts a string into a clean, URL-friendly slug.
fn slugify(s string) string {
mut res := []rune{}
mut last_was_dash := false
for r in s.to_lower().runes() {
if (r >= `a` && r <= `z`) || (r >= `0` && r <= `9`) {
res << r
last_was_dash = false
} else if r == ` ` || r == `-` || r == `_` {
if !last_was_dash && res.len > 0 {
res << `-`
last_was_dash = true
}
}
}
// Trim trailing dash if any
mut slug := res.string()
if slug.ends_with('-') {
slug = slug[0..slug.len - 1]
}
return slug
}
fn main() {
println('=== V Custom String Utilities Boilerplate ===')
// 1. Reverse String (UTF-8 safe)
phrase := 'Hello, 🚀 World!'
println('Original: "${phrase}"')
println('Reversed: "${reverse_string(phrase)}"')
// 2. Title Case (capitalizing every word)
title := 'v programming language complete textbook guide'
println('\nOriginal: "${title}"')
println('Title Case: "${title_case(title)}"')
// 3. Palindrome Check
pal1 := 'A man, a plan, a canal: Panama!'
pal2 := 'Hello Vlang'
println('\nIs "${pal1}" a palindrome? ${is_palindrome(pal1)}')
println('Is "${pal2}" a palindrome? ${is_palindrome(pal2)}')
// 4. Truncation
long_text := 'V is a statically typed compiled programming language designed for building maintainable software.'
println('\nOriginal: "${long_text}"')
println('Truncated: "${truncate(long_text, 35)}"')
// 5. Slugify
title_to_slug := ' Vlang: Concurrency, Channels, & Web APIs! '
println('\nOriginal: "${title_to_slug}"')
println('Slugified: "${slugify(title_to_slug)}"')
}
Math and Statistics Boilerplate
Math and Statistics Boilerplate
This template showcases how to perform numerical operations and custom statistical computations on numeric arrays. It implements several custom algorithmic functions that are not built directly into V's basic numeric types or standard math package.
Key concepts illustrated:
- Descriptive Statistics: Iterating over elements to calculate min, max, sum, and average (mean) on float slices.
- Array Sorting & Cloning: Using
.clone()to copy data and sorting arrays in-place using.sort(). - Median, Variance & Deviation: Custom implementations to calculate distribution variance and standard deviation using V's
math.sqrt(). - Factorial Calculation: An iterative, overflow-aware custom factorial function using V's
!result type to report overflow on inputs greater than 20. - Fibonacci Sequence Generator: A custom dynamic programming function generating the first $N$ numbers of the Fibonacci sequence, using
!to report overflow on inputs greater than 93. - Prime Number Checker: A highly optimized primality test (
is_prime) using trial division of form $6k \pm 1$. - GCD and LCM: Custom implementations for finding the Greatest Common Divisor (Euclidean algorithm) and Least Common Multiple of two numbers.
module main
import math
// Stats holds the computed statistical properties of a dataset.
struct Stats {
count int
min f64
max f64
sum f64
mean f64
median f64
variance f64
std_dev f64
}
// calculate_stats calculates standard descriptive statistics on a float dataset (not built into V arrays).
fn calculate_stats(numbers []f64) ?Stats {
if numbers.len == 0 {
return none
}
mut sum := 0.0
mut min := numbers[0]
mut max := numbers[0]
for val in numbers {
sum += val
if val < min {
min = val
}
if val > max {
max = val
}
}
mean := sum / numbers.len
// Calculate median (requires a sorted copy of the numbers)
mut sorted := numbers.clone()
sorted.sort()
mut median := 0.0
mid := sorted.len / 2
if sorted.len % 2 == 0 {
median = (sorted[mid - 1] + sorted[mid]) / 2.0
} else {
median = sorted[mid]
}
// Calculate variance and standard deviation
mut variance_sum := 0.0
for val in numbers {
diff := val - mean
variance_sum += diff * diff
}
variance := variance_sum / numbers.len
std_dev := math.sqrt(variance)
return Stats{
count: numbers.len
min: min
max: max
sum: sum
mean: mean
median: median
variance: variance
std_dev: std_dev
}
}
// factorial calculates the factorial of a number iteratively, with overflow checks.
fn factorial(n int) !u64 {
if n < 0 {
return error('Factorial is not defined for negative numbers')
}
if n > 20 {
return error('Factorial of ${n} overflows 64-bit unsigned integer limit (max n is 20)')
}
mut result := u64(1)
for i in 2 .. n + 1 {
result *= u64(i)
}
return result
}
// fibonacci generates the first n Fibonacci numbers, with overflow checks.
fn fibonacci(n int) ![]u64 {
if n < 0 {
return error('Count must be non-negative')
}
if n > 93 {
return error('Fibonacci sequence beyond 93 elements overflows 64-bit unsigned integer limit')
}
if n == 0 {
return []u64{}
}
if n == 1 {
return [u64(0)]
}
mut sequence := []u64{cap: n}
sequence << u64(0)
sequence << u64(1)
for i in 2 .. n {
sequence << sequence[i - 1] + sequence[i - 2]
}
return sequence
}
// is_prime checks if a number is prime.
fn is_prime(n int) bool {
if n <= 1 {
return false
}
if n <= 3 {
return true
}
if n % 2 == 0 || n % 3 == 0 {
return false
}
mut i := 5
for i * i <= n {
if n % i == 0 || n % (i + 2) == 0 {
return false
}
i += 6
}
return true
}
// gcd computes the Greatest Common Divisor of two integers.
fn gcd(a int, b int) int {
mut x := math.abs(a)
mut y := math.abs(b)
for y != 0 {
temp := y
y = x % y
x = temp
}
return x
}
// lcm computes the Least Common Multiple of two integers.
fn lcm(a int, b int) int {
if a == 0 || b == 0 {
return 0
}
return (math.abs(a) * math.abs(b)) / gcd(a, b)
}
fn main() {
println('=== V Custom Math & Statistics Boilerplate ===')
// 1. Descriptive Statistics Demo
data := [72.5, 81.0, 68.5, 90.0, 75.5, 78.0, 85.5]
println('Dataset: ${data}')
stats := calculate_stats(data) or {
println('Error: Empty dataset')
return
}
println('\nStatistical Results:')
println('- Count: ${stats.count}')
println('- Minimum: ${stats.min:.2f}')
println('- Maximum: ${stats.max:.2f}')
println('- Sum: ${stats.sum:.2f}')
println('- Mean (Average): ${stats.mean:.2f}')
println('- Median: ${stats.median:.2f}')
println('- Variance: ${stats.variance:.2f}')
println('- Standard Deviation: ${stats.std_dev:.2f}')
// 2. Custom Math Functions Demo
n := 10
println('\nCustom Number Functions:')
fact := factorial(n) or {
eprintln('Error: ${err}')
u64(0)
}
println('- Factorial of ${n}: ${fact}')
fib := fibonacci(n) or {
eprintln('Error: ${err}')
[]u64{}
}
println('- Fibonacci first ${n}: ${fib}')
test_primes := [7, 12, 19, 25, 97]
for p in test_primes {
println(' Is ${p} prime? ${is_prime(p)}')
}
a, b := 24, 36
println('\nCommon Number Relations:')
println('- GCD of ${a} and ${b}: ${gcd(a, b)}')
println('- LCM of ${a} and ${b}: ${lcm(a, b)}')
}
Array Utilities Boilerplate
Array Utilities Boilerplate
V's standard array implementation is powerful but focused. This template showcases how to implement useful custom generic utilities for manipulating, comparing, and organizing arrays.
Key concepts illustrated:
- Generics in V: Writing reusable algorithms using type parameters
[T]. - Deduplication (
unique): Removing duplicates from an array using containment checks (!in). - Chunking (
chunk): Partitioning an array into smaller sub-slices of a fixed maximum length. - Set Operations (
intersection,difference): Calculating overlapping elements and unique elements between two arrays. - Flattening (
flatten): Condensing nested 2D slices ([][]T) into flat 1D slices ([]T). - In-place Shuffling (
shuffle): Implementing the Fisher-Yates shuffle algorithm using V's standardrandpackage. - Weighted Selection (
weighted_choice): Selecting elements from an array based on proportional bias (weights) to simulate probability distributions.
module main
import rand
// unique returns a new array with duplicate elements removed.
fn unique[T](arr []T) []T {
mut result := []T{cap: arr.len}
for item in arr {
if item !in result {
result << item
}
}
return result
}
// chunk splits an array into sub-arrays of the specified size.
fn chunk[T](arr []T, size int) [][]T {
if size <= 0 || arr.len == 0 {
return [][]T{}
}
mut result := [][]T{}
mut current_chunk := []T{}
for item in arr {
current_chunk << item
if current_chunk.len == size {
result << current_chunk
current_chunk = []T{}
}
}
if current_chunk.len > 0 {
result << current_chunk
}
return result
}
// intersection returns a new array containing elements present in both arrays.
fn intersection[T](a []T, b []T) []T {
mut result := []T{}
for item in a {
if item in b && item !in result {
result << item
}
}
return result
}
// difference returns a new array containing elements present in a but not in b.
fn difference[T](a []T, b []T) []T {
mut result := []T{}
for item in a {
if item !in b && item !in result {
result << item
}
}
return result
}
// flatten flattens a 2D array into a 1D array.
fn flatten[T](arr [][]T) []T {
mut result := []T{}
for sub_arr in arr {
for item in sub_arr {
result << item
}
}
return result
}
// shuffle randomizes the order of elements in-place using the Fisher-Yates algorithm.
fn shuffle[T](mut arr []T) {
for i := arr.len - 1; i > 0; i-- {
j := rand.intn(i + 1) or { 0 }
temp := arr[i]
arr[i] = arr[j]
arr[j] = temp
}
}
// weighted_choice returns an element from `values` based on their corresponding `weights`.
// The lengths of `values` and `weights` must be equal and non-zero.
// All weights must be non-negative.
fn weighted_choice[T](values []T, weights []int) !T {
if values.len != weights.len {
return error('values and weights must have the same length')
}
if values.len == 0 {
return error('cannot pick from empty arrays')
}
mut total_weight := 0
for w in weights {
if w < 0 {
return error('weights must be non-negative')
}
total_weight += w
}
if total_weight <= 0 {
return error('sum of weights must be greater than zero')
}
// Pick a random number in [0, total_weight)
r := rand.intn(total_weight) or { return error('failed to generate random number: ${err}') }
mut running_sum := 0
for i, w in weights {
running_sum += w
if r < running_sum {
return values[i]
}
}
return values[values.len - 1]
}
fn main() {
println('=== V Custom Array Utilities Boilerplate ===')
// 1. Unique / Deduplication Demo
duplicates := [1, 2, 2, 3, 1, 4, 3, 5, 2]
println('Original: ${duplicates}')
println('Deduplicated: ${unique(duplicates)}')
// 2. Chunking Demo
to_chunk := ['a', 'b', 'c', 'd', 'e', 'f', 'g']
chunk_size := 3
println('\nOriginal: ${to_chunk}')
println('Chunked (${chunk_size}): ${chunk[string](to_chunk, chunk_size)}')
// 3. Intersection & Difference Demo
arr_a := [1, 2, 3, 4, 5]
arr_b := [4, 5, 6, 7, 8]
println('\nArray A: ${arr_a}')
println('Array B: ${arr_b}')
println('Intersection: ${intersection(arr_a, arr_b)}')
println('Difference: ${difference(arr_a, arr_b)}')
// 4. Flattening Demo
nested := [[1, 2], [3, 4, 5], [6]]
println('\nNested: ${nested}')
println('Flattened: ${flatten(nested)}')
// 5. In-place Shuffling Demo
mut to_shuffle := [10, 20, 30, 40, 50, 60, 70]
println('\nBefore Shuffle: ${to_shuffle}')
shuffle(mut to_shuffle)
println('After Shuffle: ${to_shuffle}')
// 6. Weighted Random Choice Demo (picking with bias)
items := ['Common', 'Uncommon', 'Rare', 'Legendary']
weights := [70, 20, 9, 1] // Sum is 100
println('\nWeighted Choice Demo (picking with bias):')
println('Items: ${items}')
println('Weights: ${weights}')
// Simulate 10,000 picks to demonstrate that the distribution matches the weights
mut stats := map[string]int{}
for _ in 0 .. 10000 {
picked := weighted_choice[string](items, weights) or {
eprintln('Error: ${err}')
continue
}
stats[picked]++
}
println('Simulation results (out of 10,000 picks):')
for item in items {
println('- ${item}: ${stats[item]} picks (~${(f64(stats[item]) / 100.0):.1f}%)')
}
}
Configuration Management Boilerplate
Configuration Management Boilerplate
Most practical applications need configuration values that can come from a file, environment variables, or defaults. This template shows a clean pattern for loading a JSON config, falling back to safe defaults, and saving updated settings.
Key concepts illustrated:
- Default Configuration: Establishing a safe baseline before reading external settings.
- Environment Variable Overrides: Adapting the app without changing source files.
- JSON Persistence: Reading and writing structured config files with
json.decodeandjson.encode. - Practical Separation of Concerns: Keeping parsing and application logic in dedicated functions.
module main
import json
import os
struct AppConfig {
mut:
host string
port int
debug bool
retries int
}
fn default_config() AppConfig {
return AppConfig{
host: '127.0.0.1'
port: 8080
debug: false
retries: 3
}
}
fn load_config(path string) AppConfig {
mut cfg := default_config()
// 1. If path is provided and exists, load config from the JSON file first
if path != '' && os.exists(path) {
raw := os.read_file(path) or {
eprintln('Warning: could not read config file: ${err}')
''
}
if raw != '' {
cfg = json.decode(AppConfig, raw) or {
eprintln('Warning: could not decode config file: ${err}')
cfg
}
}
} else if path != '' {
println('Config file not found. Using defaults with environment overrides.')
}
// 2. Overlay / override with environment variables (crucial for production container envs)
env_host := os.getenv('APP_HOST')
if env_host != '' {
cfg.host = env_host
}
env_port := os.getenv('APP_PORT')
if env_port != '' {
cfg.port = env_port.int()
}
env_debug := os.getenv('APP_DEBUG')
if env_debug != '' {
cfg.debug = env_debug == 'true'
}
env_retries := os.getenv('APP_RETRIES')
if env_retries != '' {
cfg.retries = env_retries.int()
}
return cfg
}
fn save_config(path string, cfg AppConfig) {
data := json.encode(cfg)
os.write_file(path, data) or { eprint('Failed to save config file: ${err}') }
}
fn main() {
println('=== V Configuration Management Boilerplate ===')
config_path := 'app_config.json'
// Ensure we cleanup the generated file on exit
defer {
if os.exists(config_path) {
os.rm(config_path) or {}
println('Cleaned up temporary config file: ${config_path}')
}
}
mut cfg := load_config(config_path)
println('Loaded configuration:')
println('- host: ${cfg.host}')
println('- port: ${cfg.port}')
println('- debug: ${cfg.debug}')
println('- retries: ${cfg.retries}')
cfg.debug = true
save_config(config_path, cfg)
println('Saved configuration to ${config_path}')
}
JSON File Store Boilerplate
JSON File Store Boilerplate
A simple JSON file store is often enough for small tools, prototypes, or local productivity apps. This template shows how to load persisted data from disk, add new records, update them, and save the state again.
Key concepts illustrated:
- Persistent Data: Keeping application state across runs with a simple file-backed format.
- Structured Records: Storing typed data in a
TodoItemmodel. - CRUD Style Helpers: Adding, updating, and listing records without introducing a database dependency.
- Safe Serialization: Using
json.encodeandjson.decodefor portability.
module main
import json
import os
struct TodoItem {
id int
title string
mut:
done bool
}
struct TodoStore {
mut:
items []TodoItem
}
fn load_store(path string) TodoStore {
if !os.exists(path) {
return TodoStore{}
}
raw := os.read_file(path) or {
eprintln('Could not read store: ${err}')
return TodoStore{}
}
decoded := json.decode(TodoStore, raw) or {
eprintln('Could not decode store: ${err}')
return TodoStore{}
}
return decoded
}
fn save_store(path string, store TodoStore) {
data := json.encode(store)
os.write_file(path, data) or { eprintln('Could not save store: ${err}') }
}
fn add_item(mut store TodoStore, title string) TodoItem {
item := TodoItem{
id: store.items.len + 1
title: title
done: false
}
store.items << item
return item
}
fn mark_done(mut store TodoStore, id int) bool {
for i, item in store.items {
if item.id == id {
store.items[i].done = true
return true
}
}
return false
}
fn list_items(store TodoStore) {
for item in store.items {
status := if item.done { '[x]' } else { '[ ]' }
println('${status} ${item.id}. ${item.title}')
}
}
fn main() {
println('=== V JSON File Store Boilerplate ===')
store_path := 'todos.json'
// Ensure we cleanup the generated file on exit
defer {
if os.exists(store_path) {
os.rm(store_path) or {}
println('Cleaned up temporary store file: ${store_path}')
}
}
mut store := load_store(store_path)
add_item(mut store, 'Write a V tutorial')
add_item(mut store, 'Ship a new boilerplate example')
println('Current todos:')
list_items(store)
if mark_done(mut store, 1) {
println('Marked todo #1 as done.')
} else {
println('Todo #1 was not found.')
}
save_store(store_path, store)
println('Saved todos to ${store_path}')
}
Retry and Backoff Boilerplate
Retry and Backoff Boilerplate
Transient failures are common in distributed systems, network clients, and file operations. This template demonstrates a practical retry loop that delays between attempts and surfaces a final failure only after the allowed number of tries is exhausted.
Key concepts illustrated:
- Resilient Operations: Using a generic function structure to wrap any callback in a robust retry cycle.
- Exponential Backoff: Multiplying delays by a backoff factor after each failed attempt to reduce load on resources.
- Random Jitter: Adding a small, random time variation (jitter) to sleep intervals to prevent synchronized retries (thundering herd problem).
- Result & Error Propagation: Leveraging V's
!result type to return either the successful generic valueTor propagate the final failure.
module main
import os
import time
import rand
// RetryConfig configures the retry and backoff behavior.
struct RetryConfig {
attempts int = 3
initial_delay time.Duration = 100 * time.millisecond
factor f64 = 2.0
max_delay time.Duration = 3 * time.second
jitter bool = true
}
// retry executes the operation `op` up to `cfg.attempts` times.
// It uses exponential backoff with optional random jitter.
fn retry[T](cfg RetryConfig, op fn () !T) !T {
mut delay := cfg.initial_delay
for attempt in 1 .. cfg.attempts + 1 {
res := op() or {
if attempt == cfg.attempts {
return error('Operation failed after ${cfg.attempts} attempts. Last error: ${err}')
}
eprintln('Attempt ${attempt}/${cfg.attempts} failed: ${err}. Retrying in ${delay.milliseconds()}ms...')
// Sleep with optional jitter to prevent thundering herd problems
mut sleep_dur := delay
if cfg.jitter {
jitter_ms := rand.intn(100) or { 0 }
sleep_dur += jitter_ms * time.millisecond
}
time.sleep(sleep_dur)
// Increase the delay for the next attempt, up to max_delay
delay = time.Duration(i64(f64(delay) * cfg.factor))
if delay > cfg.max_delay {
delay = cfg.max_delay
}
continue
}
return res
}
return error('Unreachable')
}
fn main() {
println('=== V Retry & Backoff Boilerplate ===')
// Create a dummy file to read successfully on the 3rd attempt
file_path := 'temp_retry_demo.txt'
defer {
if os.exists(file_path) {
os.rm(file_path) or {}
}
}
// Spawn a thread to create the file after a short delay
spawn fn [file_path] () {
time.sleep(300 * time.millisecond)
os.write_file(file_path, 'Success: Data retrieved from temporary file!') or {}
println('[System] File created on disk.')
}()
// Define our retry configuration
cfg := RetryConfig{
attempts: 4
initial_delay: 150 * time.millisecond
factor: 1.5
jitter: true
}
// Define the retriable operation closure
op := fn [file_path] () !string {
if !os.exists(file_path) {
return error('File does not exist yet')
}
return os.read_file(file_path)
}
// Run the retry loop
println('Starting resilient file read operation...')
content := retry[string](cfg, op) or {
eprintln('Final failure: ${err}')
return
}
println('\nOperation Succeeded!')
println('Read content: "${content}"')
}
HTTP Client Boilerplate
HTTP Client Boilerplate
Many real-world tools need to call web services. This template shows a simple yet practical pattern for performing HTTP GET and POST requests, decoding JSON responses, and handling failures with clear errors.
Key concepts illustrated:
- HTTP Requests: Using
net.httpfor GET and POST requests. - JSON POST Headers: Using
http.Requestdirectly to configure headers explicitly, includingContent-Type: application/jsonfor strict APIs. - JSON Payloads: Encoding and decoding structured data with
json. - Error Handling: Returning and surfacing client errors cleanly.
- Reusable Helpers: Keeping request logic in dedicated functions for later reuse.
module main
import net.http
import json
struct PostPayload {
title string @[json: 'title']
body string @[json: 'body']
user_id int @[json: 'userId']
}
struct PostResponse {
id int @[json: 'id']
title string @[json: 'title']
body string @[json: 'body']
user_id int @[json: 'userId']
}
fn fetch_json(url string) !string {
resp := http.get(url) or { return error('GET request failed: ${err}') }
if resp.status_code >= 400 {
return error('Request failed with status ${resp.status_code}')
}
return resp.body
}
fn post_json(url string, payload PostPayload) !PostResponse {
body := json.encode(payload)
// Set Content-Type explicitly for compliance with strict JSON APIs
mut req := http.Request{
method: .post
url: url
data: body
}
req.header.set(.content_type, 'application/json')
resp := req.do() or { return error('POST request failed: ${err}') }
if resp.status_code >= 400 {
return error('Request failed with status ${resp.status_code}')
}
return json.decode(PostResponse, resp.body) or { return error('Invalid JSON response') }
}
fn main() {
println('=== V HTTP Client Boilerplate ===')
body := fetch_json('https://httpbin.org/get') or {
eprintln('${err}')
return
}
println('GET response body:')
println(body)
response := post_json('https://jsonplaceholder.typicode.com/posts', PostPayload{
title: 'Ada'
body: 'Developer'
user_id: 1
}) or {
eprintln('${err}')
return
}
println('\nPOST response:')
println('id: ${response.id}')
println('title: ${response.title}')
println('body: ${response.body}')
println('user_id: ${response.user_id}')
}
CSV Processor Boilerplate
CSV Processor Boilerplate
CSV files are still common for importing and exporting tabular data. This template shows how to read records from CSV, transform them into typed structs, and write them back out in a clean format.
Key concepts illustrated:
- CSV Parsing: Reading rows with
encoding.csv. - Typed Data Models: Mapping each row into a struct.
- File I/O: Reading and writing files with
oshelpers. - Practical Data Pipelines: Transforming input files into processed output files.
module main
import os
import encoding.csv
struct Person {
name string
age int
city string
}
fn read_people(path string) ![]Person {
content := os.read_file(path) or { return error('Could not read ${path}: ${err}') }
mut reader := csv.new_reader(content)
mut people := []Person{}
for {
row := reader.read() or { break }
if row.len == 0 {
continue
}
if row[0] == 'name' {
continue
}
people << Person{
name: row[0]
age: row[1].int()
city: row[2]
}
}
return people
}
fn write_people(path string, people []Person) ! {
mut output := []string{}
output << 'name,age,city'
for person in people {
output << '${person.name},${person.age},${person.city}'
}
os.write_file(path, output.join('\n')) or { return error('Could not write ${path}: ${err}') }
}
fn main() {
println('=== V CSV Processor Boilerplate ===')
input_path := 'people.csv'
output_path := 'people_out.csv'
// Ensure temporary CSV files are cleaned up on exit
defer {
if os.exists(input_path) {
os.rm(input_path) or {}
}
if os.exists(output_path) {
os.rm(output_path) or {}
}
println('Cleaned up temporary CSV files.')
}
os.write_file(input_path, 'name,age,city\nAlice,30,New York\nBob,25,San Francisco') or {
eprintln('Could not create sample CSV: ${err}')
return
}
people := read_people(input_path) or {
eprintln('${err}')
return
}
for person in people {
println('Loaded: ${person.name} (${person.age}) from ${person.city}')
}
write_people(output_path, people) or {
eprintln('${err}')
return
}
println('Wrote processed CSV to ${output_path}')
}
macOS Native GUI Boilerplate
macOS Native GUI Boilerplate
V is not limited to command-line tools. This starter project shows how to build a native macOS desktop app with a small, beginner-friendly API over Cocoa. It is a practical bridge between simple scripting-style V code and real GUI development.
Key concepts illustrated:
- Native Window Creation: Starting a real macOS window with
simplegui.new_simple_window(...). - Named Controls: Creating labels, text inputs, buttons, checkboxes, sliders, and more with one-line helpers.
- Event-Driven UI: Connecting UI actions with callbacks such as
on_clickandon_change. - State Updates from V: Reading and writing control values with helpers like
get_text,set_text,set_checked, andset_value_int. - Crossing into Desktop Apps: Showing how V can be used for interactive desktop experiences beyond console programs.
module main
import simplegui
fn main() {
mut gui := simplegui.new_simple_window('V Native GUI Demo', 760, 950)
gui.set_title('V Native GUI Demo')
gui.add_label('intro', 'Create controls with one small call')
gui.add_input('name', 'Ada')
gui.add_button('run', 'Run')
gui.on_click('run', on_run_clicked)
gui.run()
}
fn on_run_clicked(mut win &simplegui.SimpleWindow) {
println('run clicked')
name := win.get_text('name')
win.alert('Hello', 'Hello, ${name}!')
}
The full project in this repository includes multiple demos and a reusable module implementation in boilerplate_templates/13_simplegui/simplegui/simplegui.v, making it an excellent example for learners who want to move from console programs into desktop interfaces.
For the complete source, demo list, screenshots, and setup instructions, visit the original GitHub repository: https://github.com/codecaine-zz/vlang_simplegui/tree/master
To use the project end to end, open the folder at boilerplate_templates/13_simplegui and change into that directory first so it becomes the current working directory, then run the main demo with v run main.v. This matters because the example expects to be launched from its own folder. You can also explore the other demo files in the same directory to see the available UI patterns.
Logging Boilerplate
Logging Boilerplate
Structured logging is essential for debugging, monitoring, and understanding how an application behaves over time. This template shows a simple logger that writes messages with levels, timestamps, and optional file output.
Key concepts illustrated:
- Log Levels: Using
debug,info,warn, anderrorcategories. - Simple Logger Struct: Encapsulating configuration and behavior in one reusable type.
- File Output: Appending log entries to a file for later inspection.
- Filtering: Only emitting messages at or above the configured severity level.
module main
import os
import time
enum LogLevel {
debug
info
warn
error
}
struct Logger {
log_file string
level LogLevel
}
fn (logger Logger) log(level LogLevel, message string) {
if int(level) < int(logger.level) {
return
}
timestamp := time.now().str()
prefix := match level {
.debug { '[DEBUG]' }
.info { '[INFO]' }
.warn { '[WARN]' }
.error { '[ERROR]' }
}
line := '${timestamp} ${prefix} ${message}'
println(line)
if logger.log_file != '' {
mut f := os.open_file(logger.log_file, 'a') or {
eprintln('Failed to open log file: ${err}')
return
}
f.write((line + '\n').bytes()) or { eprintln('Failed to append log: ${err}') }
f.close()
}
}
fn main() {
println('=== V Logging Boilerplate ===')
log_path := 'app.log'
defer {
if os.exists(log_path) {
os.rm(log_path) or {}
println('Cleaned up temporary log file: ${log_path}')
}
}
logger := Logger{
log_file: log_path
level: .info
}
logger.log(.debug, 'This debug message is filtered out')
logger.log(.info, 'Application started')
logger.log(.warn, 'Configuration value is missing')
logger.log(.error, 'Something went wrong')
}
Chapter 15 Comprehensive Practice Exercises
Quick Access
Below is an index of all exercises in this chapter. You can use these links to jump directly to any specific exercise:
Practice Exercises Overview
- Exercise 1: Comments and Console Printing
- Exercise 2: Circle Area Calculator with Variables and Constants
- Exercise 3: String and Rune Processing
- Exercise 4: Custom FizzBuzz with Match
- Exercise 5: Filtering and Sorting Student Grades
- Exercise 6: Higher-Order Functions with Callbacks
- Exercise 7: Modeling a Bank Account with Structs & Methods
- Exercise 8: Safe Division with Option/Result
- Exercise 9: Modular Math Utility Project
- Exercise 10: Unit Testing String Reversal
- Exercise 11: Concurrent Task Aggregation with Channels
- Exercise 12: JSON Parsing and Validation
- Exercise 13: HTTP Client & Query Parameter Parser
- Exercise 14: Concurrent Worker Pool for String Transformation
Practice Exercises Overview
Welcome to Chapter 15! This chapter contains comprehensive practice exercises designed to consolidate your learning. Each exercise targets a specific chapter from this book and includes the prompt, the correct V code solution, and the expected terminal output.
Use these exercises to test your understanding as you progress through each chapter of the textbook.
Exercise 1: Comments and Console Printing
This exercise covers Chapter 1: Getting Started with V.
Write a V program that prints your favorite programming languages to the console, utilizing single-line comments for metadata (such as Author and Date) and a multi-line comment describing the compile-to-C architecture of V.
module main
// Author: V Learner
// Date: June 2026
fn main() {
/*
This program prints a list of favorite languages.
V compiles to C, resulting in fast execution and tiny binaries.
*/
println('My favorite programming languages are:')
println('- V (for speed and simplicity)')
println('- Go (for cloud networking)')
println('- Rust (for memory safety)')
}
My favorite programming languages are:
- V (for speed and simplicity)
- Go (for cloud networking)
- Rust (for memory safety)
Exercise 2: Circle Area Calculator with Variables and Constants
This exercise covers Chapter 2: Variables and Constants.
Create a program that defines a constant pi = 3.14159. In the main function, declare a mutable variable for the radius of a circle, initialize it to 5.0, and compute/print the area. Then, update the radius to 10.0, recompute the area, and print the updated result with 2 decimal places.
module main
const pi = 3.14159
fn main() {
mut radius := 5.0
mut area := pi * radius * radius
println('Radius: ${radius} | Area: ${area:.2f}')
radius = 10.0
area = pi * radius * radius
println('Radius: ${radius} | Area: ${area:.2f}')
}
Radius: 5 | Area: 78.54
Radius: 10 | Area: 314.16
Exercise 3: String and Rune Processing
This exercise covers Chapter 3: Primitive Data Types.
Create a V program that takes a string representation of a username. Retrieve and print its length, convert the entire username to uppercase, extract the first letter as a rune, and print its character representation as well as its raw ASCII integer value.
module main
fn main() {
username := 'vlang_developer'
// Get string length
len := username.len
println('Username length: ${len}')
// Convert to uppercase
upper := username.to_upper()
println('Uppercase: ${upper}')
// Extract first character as rune
first_char := username[0]
println('First character: ${first_char.ascii_str()}')
println('ASCII value: ${int(first_char)}')
}
Username length: 15
Uppercase: VLANG_DEVELOPER
First character: v
ASCII value: 118
Exercise 4: Custom FizzBuzz with Match
This exercise covers Chapter 4: Control Flow.
Write a V program that loops from 1 to 20. For each number, determine if it is divisible by 3, 5, both, or neither. Use a match expression to print "Fizz" for multiples of 3, "Buzz" for multiples of 5, "FizzBuzz" for multiples of both, and the number itself otherwise.
module main
fn main() {
for i in 1 .. 21 {
match true {
i % 15 == 0 { println('FizzBuzz') }
i % 3 == 0 { println('Fizz') }
i % 5 == 0 { println('Buzz') }
else { println(i) }
}
}
}
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
Exercise 5: Filtering and Sorting Student Grades
This exercise covers Chapter 5: Collections: Arrays and Maps.
Create a map storing student names and their corresponding numeric grades. Filter the map to extract all students who scored 80 or above. Store these students' names in an array, sort the array alphabetically, and print the sorted names.
module main
fn main() {
grades := {
'Alice': 85
'Bob': 72
'Charlie': 90
'Diana': 65
'Ethan': 88
}
mut top_students := []string{}
for name, grade in grades {
if grade >= 80 {
top_students << name
}
}
top_students.sort()
println('Top Students (Alphabetical): ${top_students}')
}
Top Students (Alphabetical): ['Alice', 'Charlie', 'Ethan']
Exercise 6: Higher-Order Functions with Callbacks
This exercise covers Chapter 6: Functions.
Write a function filter_ints(nums []int, f fn (int) bool) []int that filters an array of integers using a callback function. In main, call filter_ints once to filter even numbers, and once to filter numbers greater than 10. Print the results.
module main
fn filter_ints(nums []int, f fn (int) bool) []int {
mut result := []int{}
for num in nums {
if f(num) {
result << num
}
}
return result
}
fn is_even(n int) bool {
return n % 2 == 0
}
fn main() {
numbers := [2, 5, 12, 7, 18, 9, 3, 22]
evens := filter_ints(numbers, is_even)
println('Even numbers: ${evens}')
greater_than_ten := filter_ints(numbers, fn (n int) bool {
return n > 10
})
println('Numbers > 10: ${greater_than_ten}')
}
Even numbers: [2, 12, 18, 22]
Numbers > 10: [12, 18, 22]
Exercise 7: Modeling a Bank Account with Structs & Methods
This exercise covers Chapter 7: Structs (Custom Types).
Define a BankAccount struct with fields owner (string), balance (f64), and is_active (bool). Implement a value receiver method to display the account details, and mutable methods to deposit(amount f64) and withdraw(amount f64). Ensure that a withdrawal cannot exceed the balance or occur on an inactive account.
module main
struct BankAccount {
owner string
mut:
balance f64
is_active bool
}
fn (a BankAccount) display() {
status := if a.is_active { 'Active' } else { 'Inactive' }
println('Account Owner: ${a.owner} | Balance: $${a.balance:.2f} | Status: ${status}')
}
fn (mut a BankAccount) deposit(amount f64) {
if !a.is_active {
println('Cannot deposit: Account is inactive.')
return
}
if amount > 0 {
a.balance += amount
println('Deposited $${amount:.2f}')
}
}
fn (mut a BankAccount) withdraw(amount f64) {
if !a.is_active {
println('Cannot withdraw: Account is inactive.')
return
}
if amount > a.balance {
println('Cannot withdraw: Insufficient funds.')
return
}
if amount > 0 {
a.balance -= amount
println('Withdrew $${amount:.2f}')
}
}
fn main() {
mut acc := BankAccount{
owner: 'Jane Doe'
balance: 150.00
is_active: true
}
acc.display()
acc.deposit(50.50)
acc.withdraw(75.00)
acc.display()
acc.withdraw(200.00)
}
Account Owner: Jane Doe | Balance: $150.00 | Status: Active
Deposited $50.50
Withdrew $75.00
Account Owner: Jane Doe | Balance: $125.50 | Status: Active
Cannot withdraw: Insufficient funds.
Exercise 8: Safe Division with Option/Result
This exercise covers Chapter 8: Error Handling.
Write a function divide(a f64, b f64) !f64 that returns an error when dividing by zero. In main, call this function, handle potential errors cleanly using an or block, and print the results for both a valid division and an invalid division.
module main
fn divide(a f64, b f64) !f64 {
if b == 0.0 {
return error('division by zero error')
}
return a / b
}
fn main() {
x := 10.0
y := 2.5
z := 0.0
res1 := divide(x, y) or {
println('Error: ${err}')
return
}
println('${x} / ${y} = ${res1}')
divide(x, z) or {
println('Error occurred: ${err}')
return
}
}
10 / 2.5 = 4
Error occurred: division by zero error
Exercise 9: Modular Math Utility Project
This exercise covers Chapter 9: Organizing Code with Modules.
Describe how to design a modular program containing a main module and a utility sub-module named mathutils. Implement a public function factorial(n int) int inside mathutils and import it into your main module to calculate and print factorial(5).
// Directory structure:
// my_project/
// ├── main.v
// └── mathutils/
// └── mathutils.v
// mathutils/mathutils.v
module mathutils
pub fn factorial(n int) int {
if n <= 1 {
return 1
}
return n * factorial(n - 1)
}
// main.v
module main
import mathutils
fn main() {
val := 5
result := mathutils.factorial(val)
println('Factorial of ${val} is ${result}')
}
Factorial of 5 is 120
Exercise 10: Unit Testing String Reversal
This exercise covers Chapter 10: Writing Tests in V.
Write a V library containing a public function reverse_string(s string) string. Write a corresponding test file reverse_string_test.v with unit tests verifying correctness for empty strings, single characters, palindromes, and multi-word sentences using assert statements.
// reverse.v
module main
pub fn reverse_string(s string) string {
mut runes := s.runes()
mut i := 0
mut j := runes.len - 1
for i < j {
temp := runes[i]
runes[i] = runes[j]
runes[j] = temp
i++
j--
}
return runes.string()
}
// reverse_string_test.v
module main
fn test_reverse_string() {
assert reverse_string('') == ''
assert reverse_string('a') == 'a'
assert reverse_string('radar') == 'radar'
assert reverse_string('hello world') == 'dlrow olleh'
}
[PASS] test_reverse_string
Exercise 11: Concurrent Task Aggregation with Channels
This exercise covers Chapter 11: Concurrency and Channels.
Write a program that spawns three concurrent v-routines. Each v-routine should compute a segment of a calculation (e.g. squaring a number) and send the result back through a shared channel. The main function should receive all three values from the channel, sum them up, and print the total.
module main
fn worker(id int, val int, ch chan int) {
println('Worker ${id} starting to calculate square of ${val}')
ch <- (val * val)
}
fn main() {
ch := chan int{cap: 3}
spawn worker(1, 4, ch)
spawn worker(2, 6, ch)
spawn worker(3, 8, ch)
mut sum := 0
for _ in 0 .. 3 {
val := <-ch
sum += val
}
println('Sum of concurrent square results: ${sum}')
}
Worker 1 starting to calculate square of 4
Worker 2 starting to calculate square of 6
Worker 3 starting to calculate square of 8
Sum of concurrent square results: 116
Exercise 12: JSON Parsing and Validation
This exercise covers Chapter 12: Working with Databases and JSON.
Define a struct representing a Task with fields id (int), title (string), and completed (bool). Write a program that takes a JSON string containing an array of tasks, parses it into a V array of Task structs, and prints the titles of the tasks that are not yet completed.
module main
import json
struct Task {
id int
title string
completed bool
}
fn main() {
raw_json := '[
{"id": 1, "title": "Buy groceries", "completed": true},
{"id": 2, "title": "Write V exercise guide", "completed": false},
{"id": 3, "title": "Compile textbook HTML", "completed": false}
]'
tasks := json.decode([]Task, raw_json) or {
println('Failed to parse JSON: ${err}')
return
}
println('Pending Tasks:')
for task in tasks {
if !task.completed {
println('- ${task.title}')
}
}
}
Pending Tasks:
- Write V exercise guide
- Compile textbook HTML
Exercise 13: HTTP Client & Query Parameter Parser
This exercise covers Chapter 13: Standard Library & Advanced Features.
Create a V program that imports the net.http and net.urllib modules. Build a small utility that sends a GET request to a public API URL or a dummy server, checks the response status code, and parses query parameters from a URL string, displaying each parameter's key and value.
module main
import net.http
import net.urllib
fn main() {
resp := http.get('https://httpbin.org/get') or {
println('Failed to send request: ${err}')
return
}
println('HTTP GET Status Code: ${resp.status_code}')
sample_url := 'https://example.com/search?q=vlang&limit=10&page=2'
parsed_url := urllib.parse(sample_url) or {
println('Failed to parse URL: ${err}')
return
}
params := parsed_url.query()
println('Parsed URL Query Parameters:')
for key, values in params {
println(' ${key}: ${values.join(', ')}')
}
}
HTTP GET Status Code: 200
Parsed URL Query Parameters:
q: vlang
limit: 10
page: 2
Exercise 14: Concurrent Worker Pool for String Transformation
This exercise covers Chapter 14: Useful Boilerplates and Application Templates.
Adapt the worker pool concurrent processing pattern to transform an array of lowercase strings to uppercase concurrently. Use a struct for Job and Result types, spawn multiple worker threads, feed the job channel, close it, and collect the results.
module main
struct Job {
id int
data string
}
struct Result {
job_id int
output string
}
fn worker(id int, jobs chan Job, results chan Result) {
for job in jobs {
println('Worker ${id} processing job ${job.id}: "${job.data}"')
results <- Result{
job_id: job.id
output: job.data.to_upper()
}
}
}
fn main() {
num_jobs := 5
num_workers := 3
jobs := chan Job{cap: num_jobs}
results := chan Result{cap: num_jobs}
for i in 1 .. (num_workers + 1) {
spawn worker(i, jobs, results)
}
words := ['apple', 'banana', 'cherry', 'date', 'elderberry']
for i, word in words {
jobs <- Job{
id: i + 1
data: word
}
}
jobs.close()
for _ in 0 .. num_jobs {
res := <-results
println('Result collected: Job ${res.job_id} output = "${res.output}"')
}
}
Worker 1 processing job 1: "apple"
Worker 2 processing job 2: "banana"
Worker 3 processing job 3: "cherry"
Worker 1 processing job 4: "date"
Worker 2 processing job 5: "elderberry"
Result collected: Job 1 output = "APPLE"
Result collected: Job 2 output = "BANANA"
Result collected: Job 3 output = "CHERRY"
Result collected: Job 4 output = "DATE"
Result collected: Job 5 output = "ELDERBERRY"
End of Tutorial
Congratulations! You have completed the comprehensive V Programming tutorial and exercise guide.