*ฅ^•ﻌ•^ฅ* ✨✨  HWisnu's blog  ✨✨ о ฅ^•ﻌ•^ฅ

Resizing array in C, Zig and Rust

Introduction

Arrays are a fundamental data structure in programming, but their fixed size can be limiting when dealing with dynamic data. This article explores how to resize arrays in C, Zig, and Rust.

Resizing Arrays in C

In C, arrays have a fixed size set at compile-time. To create resizable arrays, you can use dynamic memory allocation with malloc and realloc to allocate and reallocate memory at runtime.

#include <stdio.h>
#include <stdlib.h>

void add_to_array(int** arr, int* last_index, int* capacity, int elem) {
    if (*last_index + 1 >= *capacity) {
        int* realloc_result = realloc(*arr, sizeof(int) * *capacity * 2);
        if (realloc_result == NULL) {
            free(*arr);
            printf("Realloc failed for arr");
            exit(1);
        }
        printf("\nRealloc successfull for arr");

        *capacity *= 2;
        *arr = realloc_result;
    }

    (*arr)[*last_index] = elem;
    (*last_index)++;
}

int main() {
    int capacity = 4;
    int* arr_m = malloc(sizeof(int) * capacity);
    int last_index_m = 0;

    for (int i = 17; i < 52; i++) {
        add_to_array(&arr_m, &last_index_m, &capacity, i);
    }

    printf("\nUsing add_to_array to resize array: \n");
    printf("Capacity: %d\n", capacity);
    printf("Last index: %d\n", last_index_m);
    printf("First element: %d\n", arr_m[0]);
    printf("Last element: %d\n", arr_m[last_index_m - 1]);

    free(arr_m);

    return 0;
}

Define a function add_to_array that takes a pointer to an array, a pointer to the last index of the array, a pointer to the capacity of the array, and an element to add to the array. If the array is full, we use realloc to double its size. We then add the element to the array and increment the last index.

Resizing Arrays in Zig - using addToArray

In Zig, arrays are also stored in contiguous blocks of memory, but we can use the pageAlloc to allocate and reallocate memory for the array.

Here is an example of how to resize an array in Zig:

const std = @import("std");
const print = std.debug.print;
const pageAlloc = std.heap.page_allocator;


fn addToArray(arr: *[]i32, last_index: *usize, capacity: *usize, elem: i32) !void {
    
    if (last_index.* + 1 >= capacity.*) {
        const new_capacity = capacity.* * 2;
        const new_arr = try pageAlloc.realloc(arr.*, new_capacity * @sizeOf(i32));
        errdefer print("Realloc failed for arr\n", .{});

        arr.* = new_arr;
        capacity.* = new_capacity;
        print("Realloc successful for arr\n", .{});
    }

    arr.*[last_index.*] = elem;
    last_index.* += 1;
}


pub fn main() !void {
    
    var capacity: usize = 4;
    var arr_m = try pageAlloc.alloc(i32, capacity);
    var last_index_m: usize = 0;

    for (17..52) |i| {
        try addToArray(&arr_m, &last_index_m, &capacity, @intCast(i));
    }

    print("\nUsing addToArray to resize array: \n", .{});
    print("Capacity: {d}\n", .{capacity});
    print("Last index: {d}\n", .{last_index_m});
    print("First element: {d}\n", .{arr_m[0]});
    print("Last element: {d}\n", .{arr_m[last_index_m - 1]});
}

The addToArray function resizes an array dynamically. It takes the array, its last index, capacity, and an element to add. If the array is full, it doubles its size using pageAlloc.realloc, then adds the element and updates the last index.

Resizing Arrays in Zig - using ArrayList

Below is a built-in method that achieves the same result with addToArray above, but without the need to write a custom function.

//
// Use ArrayList to resize array
//

const std = @import("std");
const print = std.debug.print;
const pageAlloc = std.heap.page_allocator;
const arenaAlloc = std.heap.ArenaAllocator;

pub fn main() !void {

    var arena = arenaAlloc.init(pageAlloc);
    defer arena.deinit();
    const allocator = arena.allocator();

    var arr_m = std.ArrayList(i32).init(allocator);
    defer arr_m.deinit();

    for (17..52) |i| {
        try arr_m.append(@intCast(i));
    }

    print("\nUsing ArrayList to resize array: \n", .{});
    print("Capacity: {d}\n", .{arr_m.capacity});
    print("Last index: {d}\n", .{arr_m.items.len - 1});
    print("First element: {d}\n", .{arr_m.items[0]});
    print("Last element: {d}\n", .{arr_m.items[arr_m.items.len - 1]});
}

This code creates an std.ArrayList and appends elements to it using the append method. The std.ArrayList automatically resizes the array as needed. The capacity and last_index are not needed explicitly, as they are managed by the std.ArrayList.

Resizing Arrays in Rust - using Vec

In Rust, arrays are also stored in contiguous blocks of memory, but we can use the Vec type to create arrays that can be resized at runtime.

Here is an example of how to resize an array in Rust:

fn main() {
    let capacity: usize = 4;
    let mut arr_m = Vec::with_capacity(capacity);
    let mut last_index_m: usize = 0;

    for i in 17..52 {
        arr_m.push(i);
        last_index_m += 1;
    }

    println!("\nUsing Vec to resize array: ");
    println!("Capacity: {}", arr_m.capacity());
    println!("Last index: {}", last_index_m);
    println!("First element: {}", arr_m[0]);
    println!("Last element: {}", arr_m[last_index_m - 1]);
}

Define a Vec called arr_m with an initial capacity of 4. We then use a loop to add elements to the Vec, which will automatically resize as needed. We also keep track of the last index of the Vec and print out the capacity, last index, first element, and last element of the Vec.

Resizing Arrays in Rust - using DynamicArray

Create a dynamic array (a custom struct) that can resize itself as elements are added to it.

//
// Use Dynamic Array to resize array
//

pub struct DynamicArray {
    arr: Vec<i32>,
    last_index: usize,
}

This struct has two fields:

This implementation below defines several methods that can be used to interact with the DynamicArray struct.

impl DynamicArray {
    pub fn new() -> Self {
        DynamicArray {
            arr: Vec::new(),
            last_index: 0,
        }
    }

    pub fn add(&mut self, elem: i32) {
        if self.last_index >= self.arr.capacity() {
            self.arr.reserve(self.arr.capacity() * 2);
            println!("Realloc successful for arr");
        }

        self.arr.push(elem);
        self.last_index += 1;
    }

The struct also defines three methods that can be used to access the capacity, last index, and elements of the dynamic array:

    pub fn get_capacity(&self) -> usize {
        self.arr.capacity()
    }

    pub fn get_last_index(&self) -> usize {
        self.last_index
    }

    pub fn get_element(&self, index: usize) -> i32 {
        self.arr[index]
    }
}

These methods simply return the capacity, last index, and element at the specified index, respectively.

The main function:

fn main() {
    let mut arr_m = DynamicArray::new();

    for i in 17..52 {
        arr_m.add(i);
    }

    println!("\nUsing Vec to resize array: ");
    println!("Capacity: {}", arr_m.get_capacity());
    println!("Last index: {}", arr_m.get_last_index());
    println!("First element: {}", arr_m.get_element(0));
    println!("Last element: {}", arr_m.get_element(arr_m.get_last_index() - 1));
}
  1. Creates a new DynamicArray instance using the new method.
  2. Adds elements to the dynamic array using the add method in a loop that runs from 17 to 51.
  3. Prints the necessary info.

Pros and cons of using DynamicArray instead of Vec in Rust

Result

The programs should have the same output:

> Capacity: 64
> Last index: 35
> First element: 17
> Last element: 51

Conclusion

We've explored dynamic array resizing in C, Zig, and Rust. The approaches include: using malloc and realloc in C, std.heap.page_allocator in Zig, and the built-in Vec type or a custom Dynamic Array struct in Rust.


Note: pay closer attention to the C and Zig codes and how similar they are in structure. This is why I always say between C and Zig got almost 1:1 similarity in code structure. If you know C, you know Zig...and vice versa!

#array #c #low level #programming #rust #zig