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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
use crate::{types::Value, AnyObject, Object};
/// A Ruby global variable whose value lives in memory owned by Rust.
///
/// Returned by [`VM::define_variable`](struct.VM.html#method.define_variable)
/// and [`VM::define_readonly_variable`](struct.VM.html#method.define_readonly_variable).
/// Reading and writing through it does not look the variable up by name.
/// The storage is registered with the GC and kept for the life of the
/// process, since Ruby global variables cannot be removed.
#[derive(Debug)]
pub struct GlobalVariable {
address: *mut Value,
}
impl GlobalVariable {
pub(crate) fn new(address: *mut Value) -> Self {
GlobalVariable { address }
}
/// Returns the variable's current value.
///
/// # Examples
///
/// ```
/// use rutie::{Fixnum, Object, VM};
/// # VM::init();
///
/// let counter = VM::define_variable("$rutie_get_example", Fixnum::new(1));
///
/// VM::eval("$rutie_get_example += 1").unwrap();
///
/// assert_eq!(counter.get().try_convert_to::<Fixnum>(), Ok(Fixnum::new(2)));
/// ```
pub fn get(&self) -> AnyObject {
AnyObject::from(unsafe { *self.address })
}
/// Sets the variable's value, even for a read-only variable.
///
/// # Examples
///
/// ```
/// use rutie::{Fixnum, Object, VM};
/// # VM::init();
///
/// let limit = VM::define_readonly_variable("$rutie_set_example", Fixnum::new(1));
///
/// limit.set(Fixnum::new(5));
///
/// let value = VM::eval("$rutie_set_example").unwrap();
///
/// assert_eq!(value.try_convert_to::<Fixnum>(), Ok(Fixnum::new(5)));
/// ```
pub fn set<T: Object>(&self, value: T) {
// The address is a GC root (`rb_gc_register_address`), marked on
// every collection, so no write barrier is needed.
unsafe { *self.address = value.value() };
}
}
#[cfg(test)]
mod tests {
use crate::{Fixnum, Object, RString, GC, VM};
#[test]
fn test_global_variable_handle() {
crate::on_ruby_thread(|| {
let global = VM::define_variable("$rutie_global_variable_test", Fixnum::new(1));
assert_eq!(global.get().try_convert_to::<Fixnum>(), Ok(Fixnum::new(1)));
global.set(RString::new_utf8("changed"));
GC::start();
let from_ruby = VM::eval("$rutie_global_variable_test").unwrap();
assert_eq!(
from_ruby.try_convert_to::<RString>().unwrap().to_str(),
"changed"
);
VM::eval("$rutie_global_variable_test = 7").unwrap();
assert_eq!(global.get().try_convert_to::<Fixnum>(), Ok(Fixnum::new(7)));
});
}
}