-
Notifications
You must be signed in to change notification settings - Fork 563
Slice Type
A slice 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:
- Pointer - Points to the first element of the slice in the underlying array
- Length - The number of elements in the slice
- Capacity - The number of elements from the beginning of the slice to the end of the underlying array
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 sliceWhen using the := syntax without explicit type declaration, XGo automatically infers the complete slice type []ElementType based on the literal values provided.
Type Inference Rules
- Uniform Types: If all elements have the same type, that type is used.
-
Mixed Types: If elements have incompatible types, the type is inferred as
any. -
Empty Slice
[]: Inferred as[]anyby 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 typeWhen a type is explicitly declared, the literal values are converted to match the declared type.
Use make when you need an empty slice or want to optimize performance by pre-allocating capacity.
// 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 > 150The 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.
Use slice literals ([]) when:
- You have initial data to populate
- You want automatic type inference for convenience
- You prefer concise, readable code
Use slice literals with explicit type (var s []T = []) when:
- You have initial data with a specific type requirement
- You need type safety while keeping syntax concise
- You want to ensure value types are converted correctly
Use make when:
- You need a slice initialized with zero values
- You want to pre-allocate capacity for performance
- You're creating an empty slice and plan to add elements later
- Working with codebases that consistently use
make
You 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 modifiedYou 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.
The <- 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]The 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.
The 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 elementNote: 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 comprehensions for simple transformations: They're most readable when the logic is straightforward
- Consider traditional loops for complex logic: If you need multiple statements or complex conditions, a regular loop may be clearer
- Avoid excessive nesting: More than two levels of nesting can be hard to read
- Keep expressions concise: Long or complex expressions reduce readability
- Use meaningful variable names: Even in short comprehensions, clarity matters
Use list comprehensions when:
- You need a simple transformation of each element
- You're filtering based on a clear condition
- The logic fits naturally in a single expression
- You want concise, functional-style code
Use traditional loops when:
- You need multiple statements per iteration
- You have complex conditional logic
- You need to break or continue based on conditions
- You're modifying external state or have side effects
- Readability would suffer from cramming logic into a comprehension
// 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: 15nums := [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: 12nums := [10, 20, 30, 40, 50]
target := 30
contains := false
for v in nums {
if v == target {
contains = true
break
}
}
echo contains // Output: truenums := [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
}
}-
Pre-allocate capacity when size is known: Use
make([]T, 0, size)to avoid multiple reallocations -
Use
len(slice)andcap(slice): These are the recommended ways to get length and capacity -
Check bounds before accessing: Ensure indexes are within valid range
[0, len(slice)-1] - Be aware of slice sharing: Slices created by slicing share the same underlying array
-
Use the
<-operator for appending: It's more concise and idiomatic in XGo - Use meaningful variable names: Make code self-documenting
- Avoid modifying slices during iteration: Create a new slice instead
- Document slice modifications: Make it clear whether functions modify input slices
-
Use deep copies when independence is needed: Use
copyor manual copying - Consider slice capacity for performance: Pre-allocating can significantly improve performance for large slices
When 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: 5a := [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:
-
Simple Literal Syntax: Use
[]for concise slice creation - Automatic Type Inference: No need for explicit type specification in most cases
-
Intuitive Append Operations: Use the
<-operator orappendfunction for adding elements - Flexible Slicing: Create sub-slices with simple range syntax
- Multiple Iteration Styles: Choose the iteration pattern that fits your needs
By understanding these features and following best practices, you can write efficient and maintainable code that leverages the full power of XGo's slice type.