Programming Languages - Slices

A slice is a view into a contiguous sequence of elements — it has a pointer, a length, and optionally a capacity. Unlike arrays, slices don't own their data and carry no fixed size in their type signature, making them the idiomatic way to pass sequences in Go, Rust, and C++.

Concept: Array vs Slice

Array Slice
Size Fixed, part of the type ([3]int ≠ [4]int) Dynamic, not part of the type
Ownership Owns its data Borrows/views existing data
Pass to function Copies entire array Only copies pointer + length
Typical use Fixed-size buffers, stack data Function parameters, subranges

Go

In Go, a slice is a struct with three fields: a pointer to the underlying array, a length, and a capacity.

// Array (fixed size — part of the type)
a := [3]int{1, 2, 3}

// Slice literal (no size — backed by a new array)
s := []int{1, 2, 3}

// Slice from array or another slice
sub := a[1:3]  // elements at index 1 and 2: {2, 3}

// make(type, len, cap)
s2 := make([]int, 5)     // len=5, cap=5, zero-valued
s3 := make([]int, 3, 10) // len=3, cap=10

Key behaviors:

s := []int{1, 2, 3, 4, 5}

fmt.Println(len(s), cap(s)) // 5, 5

// Append grows the slice (may reallocate)
s = append(s, 6)

// Slicing shares the underlying array!
a := s[1:3]
a[0] = 99          // also modifies s[1]
fmt.Println(s[1])  // 99

// To get an independent copy:
b := make([]int, len(s[1:3]))
copy(b, s[1:3])

Gotchas:

  • Appending to a slice after slicing can overwrite the original if capacity allows
  • nil slice (var s []int) is valid and has len=0, cap=0; it's not the same as an empty slice ([]int{}) but behaves identically in most operations

Rust

Rust's slice type is &[T] (immutable) or &mut [T] (mutable). It's a fat pointer: data pointer + length.

// Array (stack-allocated, fixed size)
let a: [i32; 5] = [1, 2, 3, 4, 5];

// Slice reference (borrows the array)
let s: &[i32] = &a;
let sub: &[i32] = &a[1..3]; // elements at index 1 and 2

// Vec<T> can be dereferenced into a slice
let v: Vec<i32> = vec![1, 2, 3];
let sv: &[i32] = &v;

Prefer &[T] over &Vec<T> in function parameters:

// Less flexible — only accepts Vec
fn sum(v: &Vec<i32>) -> i32 { v.iter().sum() }

// More flexible — accepts Vec, array, slice
fn sum(s: &[i32]) -> i32 { s.iter().sum() }

// Callers:
sum(&vec![1, 2, 3]);
sum(&[1, 2, 3]);
sum(&arr[1..3]);

Mutable slices:

let mut a = [3, 1, 4, 1, 5];
let s: &mut [i32] = &mut a[..];
s.sort();
println!("{:?}", a); // [1, 1, 3, 4, 5]

Gotchas:

  • Rust's borrow checker prevents mutable aliasing — you can't hold a &mut [T] and a &[T] to overlapping regions simultaneously
  • Splitting a mutable slice: use split_at_mut to get two non-overlapping mutable halves

C++

C++ doesn't have a built-in slice type in older standards. Use std::span (C++20) or absl::Span (pre-C++20).

std::span<T> (C++20):

#include <span>
#include <vector>
#include <iostream>

void print(std::span<const int> s) {
    for (int x : s) std::cout << x << ' ';
}

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    print(v);           // works with vector
    print({v.data() + 1, 3}); // subspan: {2, 3, 4}

    int arr[] = {10, 20, 30};
    print(arr);         // works with C array
}

std::span<const T> is to std::vector<T> what std::string_view is to std::string — a non-owning, lightweight view.

absl::Span (Abseil, pre-C++20):

#include "absl/types/span.h"

void process(absl::Span<const int> data) { ... }

See Abseil Tip #93 for rationale.

Gotchas:

  • std::span does not bounds-check by default in release builds — out-of-range access is UB
  • Never store a span to a temporary — it must not outlive the data it references

Python

Python doesn't have a slice type in the same sense, but slice notation creates new list/array objects. For zero-copy views, use memoryview.

a = [1, 2, 3, 4, 5]

# Slice notation (creates a copy)
s = a[1:4]   # [2, 3, 4]
s = a[::2]   # [1, 3, 5] (every other)
s = a[::-1]  # [5, 4, 3, 2, 1] (reversed)

# memoryview — zero-copy view into bytes/bytearray
data = bytearray(b"hello world")
view = memoryview(data)
sub = view[6:]            # no copy
sub[0] = ord('W')         # modifies original
print(data)               # bytearray(b'hello World')

NumPy arrays (true zero-copy slices):

import numpy as np
a = np.array([1, 2, 3, 4, 5])
s = a[1:4]   # view, NOT a copy
s[0] = 99    # modifies a[1]
print(a)     # [1, 99, 3, 4, 5]

# Force a copy
s2 = a[1:4].copy()