08 · Error Handling
Visible resource scopes
using resource = acquisition { body } installs cleanup after a successful acquisition. The type must implement core::cleanup::Cleanup. Cleanup follows lexical exits and owned transfers disarm it. A method merely named close does not establish the protocol.
On this page
using resource = acquisition { body } installs cleanup after a successful acquisition. The type must implement core::cleanup::Cleanup. Cleanup follows lexical exits and owned transfers disarm it. A method merely named close does not establish the protocol.
Complete example
Visible resource scopes.
// Resource scopes install cleanup after successful acquisition.
var acquired = 0
struct Resource { id: int }
fn acquire(id: int) -> Result<Resource, str> {
acquired += 1
if id < 0 { .Err("acquisition") } else { .Ok(Resource { id }) }
}
impl core::cleanup::Cleanup for Resource {
fn close(self) { print("close"); print(self.id) }
}
fn read(id: int) -> Result<int, str> {
let value = using file = acquire(id)? {
defer print("body-defer")
file.id + 10
}
Result::Ok(value)
}
print(read(1).unwrap())
print(read(-1).is_err())
print(acquired)
fn early() -> int {
using file = Resource { id: 2 } { return 22 }
0
}
print(early())
fn fail() -> Result<int, str> { .Err("body") }
fn propagated() -> Result<int, str> {
let value = using file = Resource { id: 3 } { fail()? }
Result::Ok(value)
}
print(propagated().is_err())
var count = 0
:outer loop {
count += 1
using file = Resource { id: count + 3 } {
defer print("loop-defer")
if count == 1 { continue :outer }
break :outer
}
}
let tail = using outer = Resource { id: 6 } {
let inner = using inner = Resource { id: 7 } { inner.id }
inner + outer.id
}
print(tail)
@linear struct Owned { id: int }
impl core::cleanup::Cleanup for Owned {
fn close(self) { print("owned-close"); print(self.id) }
}
impl Owned {
fn finish(sink self) { self.close(); let Owned { .. } = self }
}
using owned = Owned { id: 8 } { 0 }
using owned = Owned { id: 9 } {
var __zolo_live_0 = true
owned.finish()
assert(__zolo_live_0)
0
}
fn consume(sink owned: Owned) { owned.close(); let Owned { .. } = owned }
fn seven() -> int { 7 }
fn consume_with(sink owned: Owned, number: int) { assert(number == 7); consume(owned) }
using owned = Owned { id: 10 } {
let __zolo_arg_0 = 13
consume_with(owned, seven())
assert(__zolo_arg_0 == 13)
0
}
let transferred = using owned = Owned { id: 11 } { owned }
consume(transferred)
// The implicit cleanup owns the original binding even when the body shadows it.
let shadow = using file = Resource { id: 12 } {
let file = Resource { id: 13 }
file.id
}
print(shadow)
// Generic sink transfer hands the resource to the caller and disarms cleanup.
fn moved<T: core::cleanup::Cleanup>(sink value: T) -> T {
using resource = value { resource }
}
consume(moved(Owned { id: 14 }))
macro scoped(value) { using resource = $value { consume(resource); 7 } }
print(scoped!(Owned { id: 15 }))
print(scoped!(Owned { id: 16 }))
// An owned generic factory temporary keeps its cleanup obligation.
fn acquire_owned() -> Owned { Owned { id: 17 } }
fn scoped_factory<T: core::cleanup::Cleanup>(factory: fn() -> T) -> int {
let acquired = factory()
using resource = acquired { 7 }
}
print(scoped_factory(acquire_owned))
Rules and behavior
Handlers and resource scopes · Documentation
From a checkout of the repository:
zolo examples/features/resource-scopes.zolo
Acquiring several resources
A comma-separated header acquires resources in order and installs cleanup after each successful acquisition. Cleanup runs in reverse order, including when a later acquisition fails.
// Each successful acquisition installs cleanup before the next one starts.
struct File { name: str }
fn open(name: str) -> File { File { name } }
impl core::cleanup::Cleanup for File {
fn close(self) { print("close {self.name}") }
}
let copied = using source = open("source"), target = open("target") {
print("{source.name} -> {target.name}")
3
}
// expect: source -> target
// expect: close target
// expect: close source
print(copied) // expect: 3
See also