Featured image of post Exploring the RAII Pattern

Exploring the RAII Pattern

in C++, C#, Python, Go, Rust, JavaScript, and TypeScript

Photographer Waits Hours To Capture The Autumn Idyll Of Squirrels Carrying A Nut Over A Lake
https://www.boredpanda.com/squirrel-animals-photography-dick-van-duijn/

Resource Acquisition Is Initialization (RAII) is a programming pattern primarily known from C++, where resource management is tied to object lifetimes.

Let’s break it down and see how different languages handle resource management.

1. RAII in C++

C++ is the birthplace of RAII. Here, resources like memory, file handles, and locks are acquired in the constructor and released in the destructor.

Example: File Management in C++

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <fstream>

class FileWrapper {
public:
    FileWrapper(const std::string& filename) {
        file.open(filename);
        if (!file) {
            throw std::runtime_error("Failed to open file");
        }
    }

    ~FileWrapper() {
        if (file.is_open()) {
            file.close();
        }
    }

    void write(const std::string& text) {
        file << text;
    }

private:
    std::ofstream file;
};

int main() {
    try {
        FileWrapper file("example.txt");
        file.write("Hello, RAII!");
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
    }
}

Here, the destructor automatically cleans up the file when the FileWrapper object goes out of scope. No need to manually close the file!

2. RAII in C#

C# does not have destructors in the same way as C++, but it provides IDisposable and using blocks to achieve similar behavior.

Example: File Management in C#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System;
using System.IO;

class FileWrapper : IDisposable {
    private StreamWriter file;

    public FileWrapper(string filename) {
        file = new StreamWriter(filename);
    }

    public void Write(string text) {
        file.Write(text);
    }

    public void Dispose() {
        file?.Dispose();
    }
}

class Program {
    static void Main() {
        using (var file = new FileWrapper("example.txt")) {
            file.Write("Hello, RAII in C#!");
        }
    }
}

C#’s using statement ensures that Dispose() is called automatically, mimicking RAII behavior.

3. RAII in Python

Python does not have destructors like C++, but it provides context managers (with statement) via the __enter__ and __exit__ methods.

Example: File Management in Python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class FileWrapper:
    def __init__(self, filename):
        self.file = open(filename, 'w')

    def __enter__(self):
        return self.file

    def __exit__(self, exc_type, exc_value, traceback):
        self.file.close()

with FileWrapper("example.txt") as file:
    file.write("Hello, RAII in Python!")

Python’s context manager ensures the file is closed when exiting the with block.

4. RAII in Go

Go relies on explicit resource cleanup via defer, as it does not have RAII or destructors.

Example: File Management in Go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import (
	"fmt"
	"os"
)

func main() {
	file, err := os.Create("example.txt")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer file.Close() // Ensures cleanup

	_, _ = file.WriteString("Hello, RAII in Go!")
}

defer ensures that file.Close() is called when main() exits, acting as a manual RAII alternative.

5. RAII in Rust

Rust fully embraces RAII, enforcing strict ownership rules with automatic cleanup when an object goes out of scope.

Example: File Management in Rust

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use std::fs::File;
use std::io::Write;

struct FileWrapper {
    file: File,
}

impl FileWrapper {
    fn new(filename: &str) -> std::io::Result<FileWrapper> {
        let file = File::create(filename)?;
        Ok(FileWrapper { file })
    }

    fn write(&mut self, text: &str) -> std::io::Result<()> {
        self.file.write_all(text.as_bytes())
    }
}

fn main() {
    if let Ok(mut file) = FileWrapper::new("example.txt") {
        let _ = file.write("Hello, RAII in Rust!");
    }
} // file is automatically closed here

Rust enforces resource cleanup by design—once file goes out of scope, it’s dropped automatically.

6. RAII in JavaScript and TypeScript

JavaScript (and TypeScript) do not support RAII natively. Cleanup is usually handled via try-finally or explicit methods.

Example: File Management in Node.js (JavaScript/TypeScript)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import * as fs from 'fs';

class FileWrapper {
    private file: fs.WriteStream;

    constructor(filename: string) {
        this.file = fs.createWriteStream(filename);
    }

    write(text: string) {
        this.file.write(text);
    }

    close() {
        this.file.end();
    }
}

// Usage
const file = new FileWrapper("example.txt");
try {
    file.write("Hello, RAII in JavaScript!");
} finally {
    file.close();
}

Here, finally ensures the file is closed, but this requires manual handling.

Conclusion

LanguageRAII SupportAlternative Mechanism
C++✅ FullDestructors (~Class())
C#⚠️ PartialIDisposable + using
Python⚠️ Partialwith statement
Go❌ Nodefer
Rust✅ FullOwnership model
JavaScript❌ Notry-finally
TypeScript❌ Notry-finally

C++ and Rust fully support RAII. C# and Python provide structured alternatives, while Go and JavaScript require manual cleanup.

If you’re coming from a C++ background and miss RAII, Rust is your best bet. Otherwise, learning the language-specific cleanup patterns is key.


Key Ideas

ConceptSummary
RAII in C++Uses destructors to manage resources automatically.
RAII in C#Uses IDisposable and using for cleanup.
RAII in PythonUses context managers (with).
RAII in GoUses defer for manual cleanup.
RAII in RustUses ownership and automatic resource cleanup.
RAII in JavaScript/TypeScriptRequires manual try-finally handling.