User procedure will leak memory
#[derive(Clone, Debug, PartialEq)]
pub struct StandardEnv<R: RealNumberInternalTrait> {
parent: Option<Rc<StandardEnv<R>>>,
definitions: RefCell<HashMap<String, Value<R, StandardEnv<R>>>>,
}
...
#[derive(Debug, Clone, PartialEq)]
pub enum Procedure<R: RealNumberInternalTrait, E: IEnvironment<R>> {
User(SchemeProcedure, Rc<E>),
Buildin(BuildinProcedure<R, E>),
}
while user procedure carrying an rc environment, then the environment having the definitions that contains the procedure, the rc environment will never release
A closure will be released when out leave the scope using it, thus the referenced environment would be released.
(lambda () ; env1
(define foo
(lambda () ; env2 with var `count`
(define count 0)
(lambda () (+ count 1) count)
); env2 rc count-1 = 1
)
(define bar (foo)) ; make env2 live with env1
(bar) ;1
(bar) ;2
)
; releasing env1 will cause releasing foo, which will release env2
; an root env1, rc count = 1 (interpreter::env)
(define foo
(lambda ()
)
); an user procedure (foo's AST, env1), clone the env1 rc , rc count + 1 = 2, then move into env1::definitions
; interpreter destroy, env1 rc count -1 = 1
; env1 leak
impl<R: RealNumberInternalTrait, E: IEnvironment<R>> Interpreter<R, E> {
...
pub fn eval_expression(expression: &Expression, env: &Rc<E>) -> Result<Value<R, E>> {
...
ExpressionBody::Procedure(scheme) => {
Value::Procedure(Procedure::User(scheme.clone(), env.clone()))
}
...
}
...
}
I see it, seems a complete closure cannot be implemented without gc.
If consider unsafe closure, like in C++, this problem can be solved by some unsafe code representing capture by reference. As a interpreter using gc is the safest way. But if consider compiling to LLVM IR, we will end up with 3 situations:
- unsafe scheme
- native code with a native gc
- rust-like lifetime checker to reject unsafe use of closure