How can I run clean-up code in a Rust library?
Asked Answered
M

2

6

I am making an crossplatform terminal library. Because my library changes the state of the terminal, I need to revert all the changes that are made to the terminal when the process ends. I am now implementing this feature and thinking of ways how to restore to the original terminal state at the end.

I thought that a static variable is initialized when the program starts and that when the program ends this static variable will be destroyed. Since my static variable is a struct which has implemented the Drop trait, it would be dropped at the end of the program, but this is not the case because the string "drop called" is never printed:

static mut SOME_STATIC_VARIABLE: SomeStruct = SomeStruct { some_value: None };

struct SomeStruct {
    pub some_value: Option<i32>,
}

impl Drop for SomeStruct {
    fn drop(&mut self) {
        println!("drop called");
    }
}

Why is drop() not called when the program ends? Are my thoughts wrong and should I do this another way?

Matthiew answered 11/2, 2018 at 14:9 Comment(3)
See RFC - Allow Drop types in statics/const functions and (slightly outdated) doc.rust-lang.org/reference/items/static-items.htmlLovellalovelock
So as the docs say: &quot;Statics may not contain any destructors.&quot;, how should I then run some code when the proccess ends. I think I can make it work but any suggestions?Matthiew
See also: How to make a Rust singleton's destructor run?.Unguentum
B
8

One way to enforce initialization and clean-up code in a library is to introduce a Context type that can only be constructed with a public new() function, and implementing the Drop trait. Every function in the library requiring initialization can take a Context as argument, so the user needs to create one before calling these functions. Any clean-up code can be included in Context::drop().

pub struct Context {
    // private field to enforce use of Context::new()
    some_value: Option<i32>,
}

impl Context {
    pub fn new() -> Context {
        // Add initialization code here.
        Context { some_value: Some(42) }
    }
}

impl Drop for Context {
    fn drop(&mut self) {
        // Add cleanup code here
        println!("Context dropped");
    }
}

// The type system will statically enforce that the initialization
// code in Context::new() is called before this function, and the
// cleanup code in drop() when the context goes out of scope.
pub fn some_function(_ctx: &Context, some_arg: i32) {
    println!("some_function called with argument {}", some_arg);
}
Bully answered 12/2, 2018 at 21:2 Comment(1)
Marked your answer because it is an way of solving the problem.Matthiew
C
7

One of the principles of Rust is no life before main, which implies no life after main.

There are considerable challenges in correctly ordering constructors and destructors before or after main. In C++ the situation is referred to as static initialization order fiasco, and while there are work-arounds for it, its pendant (static destruction order fiasco) has none.

In Rust, the challenge is exacerbated by the 'static lifetime: running a destructor in statics could lead to observing partially destructed other statics. Which is unsafe.

In order to allow safe destruction of statics, the language would need to introduce subsets of 'static lifetimes to order the construction/destruction of statics while having those lifetimes still be 'static from inside main...


How to run code at the start/end of the program?

Simply run code at the start/end of main. Note that any structure built at the beginning of main will be dropped at its end in reverse order of construction.

And if I am not writing main myself?

Ask the writer of main, nicely.

Calibre answered 11/2, 2018 at 16:5 Comment(1)
Thanks for the information, makes sense. Since I am writing an library maybe asking the user to execute some function from my library at the could be a thing. I'll have to think about it.Matthiew

© 2022 - 2024 — McMap. All rights reserved.