Skip to content

Suggestion: Wrapping malloc/free-able resources in "RAII" classes on the Swift side #53

@DivineDominion

Description

@DivineDominion

In @ricobeck's presentation today we looked at this part:

@discardableResult public func setSample(path: UnsafePointer<CChar>) -> Bool {
if let samplePointer {
sample.freeSample(samplePointer)
}
samplePointer = sample.load(path)
guard samplePointer != nil else { return false }
sampleplayer.setSample(playerPointer, samplePointer)
return true
}

Suggestion

What do you think about this:

We wrap a manually memory-managed object like "Sample" in a RAII (Resource Acquisition Is Initialization) class. (I know it's a C++ technique https://en.cppreference.com/w/cpp/language/raii but works with C as well to hide pointer mangement)

Before

 @discardableResult public func setSample(path: UnsafePointer<CChar>) -> Bool { 
     // Every change to self.sample needs to care about memory
     if let samplePointer { 
         sample.freeSample(samplePointer) 
     } 
  
     // Replacing can fail (but the other pointer is already gone)
     samplePointer = sample.load(path) 
     guard samplePointer != nil else { return false } 
  
     // Still using raw pointers after the load, also for playerPointer
     sampleplayer.setSample(playerPointer, samplePointer) 
     return true 
 } 

After

 @discardableResult public func setSample(path: UnsafePointer<CChar>) -> Bool { 
     guard let newSample = Sample(loadFromPath: path) else { return false }
     self.sample = newSample  
     newSample.withUnsafePointer { samplePointer in
        sampleplayer.setSample(playerPointer, samplePointer) 
     }
     return true 
 } 

Approach

Pseudoswift sketch:

final class Sample {
    private let pointer: OpaquePointer

    init?(loadFromPath path: UnsafePointer<CChar>) {
        let pointer = sample.load(path)
        guard pointer != nil else { return nil }
        self.pointer = pointer
    }

    deinit {
        sample.freeSample(pointer)
    }

    func withUnsafePointer(_ body: (OpaquePointer) -> Void) {
        body(pointer)
    }
}
  • Upside: if you have a Sample, your pointer is still alive, and we can use ARC to manage Sample's lifetime. So no calls to 'free' outside of where the opaque pointers are privately known.
  • Downside: It's another heap allocation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions