doc/slice.md
A slice (also named list) is a dynamically-sized, flexible view into the elements of an array. Slices are one of the most commonly used data structures in XGo, providing efficient and convenient ways to work with sequences of elements.
Note: In XGo, the terms slice and list are identical and refer to the same data structure. The term "slice" comes from Go's terminology, while "list" aligns with Python's naming convention.
A slice consists of three components:
Unlike arrays which have a fixed size, slices can grow and shrink dynamically, making them ideal for most collection use cases.
XGo provides multiple ways to create slices: using slice literals for quick initialization with data, using the make function for more control over slice types and capacity, and slicing existing arrays or slices.
In XGo, you can create slices using square brackets []:
a := [1, 2, 3] // []int
b := [1, 2, 3.4] // []float64
c := ["Hi"] // []string
d := ["Hi", 10] // []any - mixed types
e := [] // []any - empty slice
When using the := syntax without explicit type declaration, XGo automatically infers the complete slice type []ElementType based on the literal values provided.
Type Inference Rules
any.[]: Inferred as []any by default for maximum flexibility.You can also explicitly specify the slice type to override automatic type inference:
// Explicit type declaration
var a []float64 = [1, 2, 3] // Values converted to float64
var c []any = ["x", 1, true] // Explicit any type
When a type is explicitly declared, the literal values are converted to match the declared type.
makeUse make when you need an empty slice or want to optimize performance by pre-allocating capacity.
make Syntax// Create slice with specified length (initialized to zero values)
s1 := make([]int, 5) // [0, 0, 0, 0, 0]
s2 := make([]string, 3) // ["", "", ""]
// Access and modify
s1[0] = 100
s1[2] = 300
echo s1 // Output: [100 0 300 0 0]
For performance optimization, you can specify both length and capacity:
// Create slice with length 0 and capacity 100
s := make([]int, 0, 100)
// This doesn't limit the slice size, but helps reduce allocations
for i := 0; i < 150; i++ {
s <- i
}
echo len(s) // Output: 150
echo cap(s) // Output: likely > 150
The capacity hint doesn't limit the slice's size but helps the runtime allocate memory more efficiently when you know approximately how many elements you'll add.
make vs LiteralsUse slice literals ([]) when:
Use slice literals with explicit type (var s []T = []) when:
Use make when:
makeYou can create new slices by slicing existing arrays or slices using the range syntax [start:end]:
arr := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// Basic slicing
slice1 := arr[2:5] // [3, 4, 5] - from index 2 to 5 (exclusive)
slice2 := arr[:3] // [1, 2, 3] - from start to index 3
slice3 := arr[5:] // [6, 7, 8, 9, 10] - from index 5 to end
slice4 := arr[:] // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - full slice (shallow copy)
Important: Slices created this way share the same underlying array. Modifying one slice may affect others:
arr := [1, 2, 3, 4, 5]
slice1 := arr[1:4] // [2, 3, 4]
slice2 := arr[2:5] // [3, 4, 5]
slice1[1] = 100 // Modifies the shared underlying array
echo slice1 // Output: [2 100 4]
echo slice2 // Output: [100 4 5] - also affected!
echo arr // Output: [1 2 100 4 5] - original array modified
You can directly modify elements at specific indexes:
nums := [1, 2, 3, 4, 5]
nums[0] = 100
nums[2] = 300
echo nums // Output: [100 2 300 4 5]
XGo provides two ways to append elements to slices: the <- operator and the append built-in function.
<- OperatorThe <- operator provides an intuitive way to append elements:
nums := [1, 2, 3]
nums <- 4 // Append single element
nums <- 5, 6, 7 // Append multiple elements
more := [8, 9, 10]
nums <- more... // Append another slice
echo nums // Output: [1 2 3 4 5 6 7 8 9 10]
append FunctionThe append function returns a new slice with elements added or removed:
// Adding elements
nums := [1, 2, 3]
nums = append(nums, 4) // Append single element
nums = append(nums, 5, 6, 7) // Append multiple elements
more := [8, 9, 10]
nums = append(nums, more...) // Append another slice
echo nums // Output: [1 2 3 4 5 6 7 8 9 10]
Important: The append function returns a new slice, so you must assign the result back to a variable.
appendThe append function can also remove consecutive elements by concatenating slices before and after the range to remove:
nums := [1, 2, 3, 4, 5]
// Remove element at index 2 (value 3)
nums = append(nums[:2], nums[3:]...)
echo nums // Output: [1 2 4 5]
// Remove multiple consecutive elements (indices 1-2)
nums = [1, 2, 3, 4, 5]
nums = append(nums[:1], nums[3:]...)
echo nums // Output: [1 4 5]
This pattern uses slice notation to select everything before the removal range (nums[:start]) and everything after it (nums[end:]), then concatenates them together. This effectively removes the elements in the slice nums[start:end].
Indexes start from 0. Valid indexes range from 0 to len(slice) - 1:
nums := [10, 20, 30, 40, 50]
echo nums[0] // 10 - first element
echo nums[1] // 20 - second element
echo nums[4] // 50 - last element
Note: Negative indexing is not supported. Using an index outside the valid range will cause a runtime error.
You can get the length and capacity of a slice using the len and cap functions:
nums := [1, 2, 3, 4, 5]
echo len(nums) // 5 - number of elements
echo cap(nums) // 5 - capacity (may be larger)
You can extract portions of a slice using the range syntax:
nums := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
echo nums[2:5] // [2 3 4] - from index 2 to 5 (exclusive)
echo nums[:3] // [0 1 2] - from start to index 3
echo nums[5:] // [5 6 7 8 9] - from index 5 to end
echo nums[:] // [0 1 2 3 4 5 6 7 8 9] - full slice (shallow copy)
Remember: Sub-slices share the underlying array with the original slice. See "Creating Slices from Arrays or Slices" for details on this behavior.
XGo provides multiple ways to iterate over slices using for loops:
nums := [10, 20, 30, 40, 50]
for i, v in nums {
echo "Index:", i, "Value:", v
}
nums := [10, 20, 30, 40, 50]
for v in nums {
echo v
}
nums := [10, 20, 30, 40, 50]
for i, _ in nums {
echo "Index:", i
}
List comprehensions provide a concise and expressive way to create new lists by transforming or filtering existing sequences. They follow a syntax similar to Python's list comprehensions.
The general form of a list comprehension is:
[expression for vars in iterable]
This creates a new list where each element from the iterable is transformed by the expression.
// Square all numbers
numbers := [1, 2, 3, 4, 5]
squares := [v * v for v in numbers]
echo squares // Output: [1 4 9 16 25]
// Convert to strings
words := ["hello", "world"]
upper := [v.toUpper for v in words]
echo upper // Output: ["HELLO" "WORLD"]
// Extract from index-value pairs
doubled := [v * 2 for i, v in numbers]
echo doubled // Output: [2 4 6 8 10]
// Generate sequence
nums := [i for i in 1:11]
echo nums // Output: [1 2 3 4 5 6 7 8 9 10]
// With transformation
evens := [i * 2 for i in :5]
echo evens // Output: [0 2 4 6 8]
// With step
odds := [i for i in 1:10:2]
echo odds // Output: [1 3 5 7 9]
Add an if clause to filter elements:
[expression for vars in iterable if condition]
numbers := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// Only even numbers
evens := [v for v in numbers if v % 2 == 0]
echo evens // Output: [2 4 6 8 10]
// Only numbers greater than 5
large := [v for v in numbers if v > 5]
echo large // Output: [6 7 8 9 10]
// Filter and transform
evenSquares := [v * v for v in numbers if v % 2 == 0]
echo evenSquares // Output: [4 16 36 64 100]
numbers := [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
// Elements at even indices
evenIndexValues := [v for i, v in numbers if i % 2 == 0]
echo evenIndexValues // Output: [10 30 50 70 90]
List comprehensions can be nested to work with multi-dimensional data:
// Flatten a 2D list
matrix := [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened := [num for row in matrix for num in row]
echo flattened // Output: [1 2 3 4 5 6 7 8 9]
// Create multiplication table
table := [[i * j for j in 1:6] for i in 1:6]
echo table
// Output: [[1 2 3 4 5] [2 4 6 8 10] [3 6 9 12 15] [4 8 12 16 20] [5 10 15 20 25]]
// Extract diagonal elements
diagonal := [matrix[i][i] for i in :len(matrix)]
echo diagonal // Output: [1 5 9]
Use list comprehensions when:
Use traditional loops when:
// Good use of comprehension
squares := [x * x for x in :10]
// Better as a traditional loop (side effects, complex logic)
results := []
for x in :10 {
result := complexCalculation(x)
if result > threshold {
results <- result
updateGlobalState(result)
}
}
nums := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens := []
for v in nums {
if v % 2 == 0 {
evens <- v
}
}
echo evens // Output: [2 4 6 8 10]
nums := [1, 2, 3, 4, 5]
squared := []
for v in nums {
squared <- v * v
}
echo squared // Output: [1 4 9 16 25]
nums := [10, 20, 30, 40, 50]
target := 30
found := false
index := -1
for i, v in nums {
if v == target {
found = true
index = i
break
}
}
if found {
echo "Found", target, "at index", index
} else {
echo target, "not found"
}
nums := [1, 2, 3, 4, 5]
reversed := []
for i := len(nums) - 1; i >= 0; i-- {
reversed <- nums[i]
}
echo reversed // Output: [5 4 3 2 1]
nums := [1, 2, 2, 3, 3, 3, 4, 5, 5]
unique := []
seen := {}
for v in nums {
if !seen[v] {
unique <- v
seen[v] = true
}
}
echo unique // Output: [1 2 3 4 5]
a := [1, 2, 3]
b := [4, 5, 6]
c := [7, 8, 9]
merged := []
merged <- a...
merged <- b...
merged <- c...
echo merged // Output: [1 2 3 4 5 6 7 8 9]
nums := [1, 2, 3, 4, 5]
sum := 0
for v in nums {
sum += v
}
echo sum // Output: 15
nums := [34, 12, 67, 23, 89, 45]
max := nums[0]
min := nums[0]
for v in nums {
if v > max {
max = v
}
if v < min {
min = v
}
}
echo "Max:", max // Output: 89
echo "Min:", min // Output: 12
nums := [10, 20, 30, 40, 50]
target := 30
contains := false
for v in nums {
if v == target {
contains = true
break
}
}
echo contains // Output: true
nums := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens := []
odds := []
for v in nums {
if v % 2 == 0 {
evens <- v
} else {
odds <- v
}
}
echo evens // Output: [2 4 6 8 10]
echo odds // Output: [1 3 5 7 9]
nested := [[1, 2], [3, 4], [5, 6]]
flat := []
for subslice in nested {
flat <- subslice...
}
echo flat // Output: [1 2 3 4 5 6]
stack := []
// Push elements
stack <- 1
stack <- 2
stack <- 3
echo stack // Output: [1 2 3]
// Pop element
if len(stack) > 0 {
top := stack[len(stack) - 1]
stack = stack[:len(stack) - 1]
echo "Popped:", top // Output: Popped: 3
echo stack // Output: [1 2]
}
queue := []
// Enqueue elements
queue <- 1
queue <- 2
queue <- 3
echo queue // Output: [1 2 3]
// Dequeue element
if len(queue) > 0 {
front := queue[0]
queue = queue[1:]
echo "Dequeued:", front // Output: Dequeued: 1
echo queue // Output: [2 3]
}
nums := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
windowSize := 3
for i := 0; i <= len(nums) - windowSize; i++ {
window := nums[i:i + windowSize]
echo "Window:", window
}
// Output:
// Window: [1 2 3]
// Window: [2 3 4]
// Window: [3 4 5]
// ...
// Group items by category
items := ["apple", "banana", "carrot", "date", "eggplant"]
groups := make(map[string][]string)
for item in items {
firstLetter := item[0:1]
groups[firstLetter] <- item
}
// Access grouped data
for category, itemList in groups {
echo "Category:", category
for item in itemList {
echo " -", item
}
}
make([]T, 0, size) to avoid multiple reallocationslen(slice) and cap(slice): These are the recommended ways to get length and capacity[0, len(slice)-1]<- operator for appending: It's more concise and idiomatic in XGocopy or manual copyingWhen a slice's capacity is exceeded during append operations, XGo allocates a new underlying array with increased capacity:
s := []
echo len(s), cap(s) // Output: 0 0
s <- 1
echo len(s), cap(s) // Output: 1 1
s <- 2
echo len(s), cap(s) // Output: 2 2
s <- 3
echo len(s), cap(s) // Output: 3 4 (capacity doubled)
s <- 4, 5
echo len(s), cap(s) // Output: 5 8 (capacity doubled again)
The exact growth strategy may vary, but typically capacity doubles when exceeded.
Pre-allocating capacity avoids multiple reallocations:
// Inefficient - multiple reallocations
inefficient := []
for i := 0; i < 1000; i++ {
inefficient <- i
}
// Efficient - single allocation
efficient := make([]int, 0, 1000)
for i := 0; i < 1000; i++ {
efficient <- i
}
When creating a small slice from a large slice, the underlying array is still retained:
// May cause memory leak
func getFirstThree(data []int) []int {
return data[:3] // Still references the entire underlying array
}
// Better approach - create independent slice
func getFirstThree(data []int) []int {
result := make([]int, 3)
copy(result, data[:3])
return result
}
nums := [1, 2, 3]
// This will cause a runtime error
// echo nums[10] // Error: index out of range
// Always check bounds
index := 10
if index >= 0 && index < len(nums) {
echo nums[index]
} else {
echo "Index out of bounds"
}
nums := [1, 2, 3, 4, 5]
// This is NOT valid in XGo
// echo nums[-1] // Error: invalid slice index
// To access last element, use:
echo nums[len(nums) - 1] // Output: 5
a := [1, 2, 3]
b := a // b references same underlying array
b[0] = 100
echo a // Output: [100 2 3] - a is also modified!
// To avoid this, make a copy
c := make([]int, len(a))
copy(c, a)
c[0] = 200
echo a // Output: [100 2 3] - a is not affected
// Careful with slice of slices
matrix := []
row := [1, 2, 3]
matrix <- row
matrix <- row // Both rows reference the same underlying array!
row[0] = 100
echo matrix // Output: [[100 2 3] [100 2 3]] - both rows are modified!
// Better approach - create independent rows
matrix := []
matrix <- [1, 2, 3]
matrix <- [1, 2, 3] // Each row is independent
// Avoid this - may cause unexpected behavior
nums := [1, 2, 3, 4, 5]
for i, v in nums {
if v % 2 == 0 {
nums <- v * 2 // Modifying during iteration - risky!
}
}
// Better approach - create new slice
result := []
for v in nums {
if v % 2 == 0 {
result <- v * 2
} else {
result <- v
}
}
XGo's slices provide a powerful and flexible way to work with sequences of elements. Key features include:
[] for concise slice creation<- operator or append function for adding elementsBy understanding these features and following best practices, you can write efficient and maintainable code that leverages the full power of XGo's slice type.