mirror of https://github.com/tildeio/helix
Add Symbol coercion
The main use case for symbols is to use them as a HashMap key. However, this introduces a GC problem – we cannot store Ruby values in the heap without properly marking/registering them, otherwise they might get GC'ed by Ruby unexpectedly. (In fact, this is already a problem if you have a `Vec<VALUE>`.) I tried to avoid introducing additional problems by pinning down any symbols that goes thought the coercion protocol. This is probably overly aggressive, as it would cause any dynamic symbols (e.g. `String#to_sym`) to become un-GC-able. We can revisit this once we have a more general-purpose system to encode pinning semantics in the type system.
This commit is contained in:
parent
65a98c65d1
commit
0cdd075c22
|
@ -22,7 +22,7 @@ pub type c_string = *const libc::c_char;
|
|||
// pub type c_func = extern "C" fn(...);
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)]
|
||||
pub struct ID(*mut void);
|
||||
|
||||
#[repr(C)]
|
||||
|
@ -30,7 +30,7 @@ pub struct ID(*mut void);
|
|||
pub struct VALUE(*mut void);
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub struct RubyException(isize);
|
||||
|
||||
impl RubyException {
|
||||
|
@ -163,6 +163,9 @@ extern "C" {
|
|||
#[link_name = "HELIX_T_FALSE"]
|
||||
pub static T_FALSE: isize;
|
||||
|
||||
#[link_name = "HELIX_T_SYMBOL"]
|
||||
pub static T_SYMBOL: isize;
|
||||
|
||||
#[link_name = "HELIX_T_FIXNUM"]
|
||||
pub static T_FIXNUM: isize;
|
||||
|
||||
|
@ -188,6 +191,10 @@ extern "C" {
|
|||
pub fn rb_sprintf(specifier: c_string, ...) -> VALUE;
|
||||
pub fn rb_inspect(value: VALUE) -> VALUE;
|
||||
pub fn rb_intern(string: c_string) -> ID;
|
||||
pub fn rb_intern_str(string: VALUE) -> ID;
|
||||
pub fn rb_sym2id(symbol: VALUE) -> ID;
|
||||
pub fn rb_id2sym(id: ID) -> VALUE;
|
||||
pub fn rb_id2str(id: ID) -> VALUE;
|
||||
pub fn rb_ary_new_capa(capa: isize) -> VALUE;
|
||||
pub fn rb_ary_entry(ary: VALUE, offset: isize) -> VALUE;
|
||||
pub fn rb_ary_push(ary: VALUE, item: VALUE) -> VALUE;
|
||||
|
|
|
@ -11,13 +11,13 @@ describe "TextTransform" do
|
|||
|
||||
it "can widen hash" do
|
||||
expect(TextTransform.widen_hash({
|
||||
"message" => "Hello",
|
||||
"name" => "Aaron",
|
||||
"handle" => "@tenderlove"
|
||||
message: "Hello",
|
||||
name: "Aaron",
|
||||
handle: "@tenderlove"
|
||||
})).to eq({
|
||||
"message" => "Hello",
|
||||
"name" => "Aaron",
|
||||
"handle" => "@tenderlove"
|
||||
"message": "Hello",
|
||||
"name": "Aaron",
|
||||
"handle": "@tenderlove"
|
||||
})
|
||||
end
|
||||
|
||||
|
@ -31,13 +31,13 @@ describe "TextTransform" do
|
|||
|
||||
it "can narrowen hash" do
|
||||
expect(TextTransform.narrowen_hash({
|
||||
"message" => "Hello",
|
||||
"name" => "Aaron",
|
||||
"handle" => "@tenderlove"
|
||||
"message": "Hello",
|
||||
"name": "Aaron",
|
||||
"handle": "@tenderlove"
|
||||
})).to eq({
|
||||
"message" => "Hello",
|
||||
"name" => "Aaron",
|
||||
"handle" => "@tenderlove"
|
||||
message: "Hello",
|
||||
name: "Aaron",
|
||||
handle: "@tenderlove"
|
||||
})
|
||||
end
|
||||
|
||||
|
@ -51,13 +51,13 @@ describe "TextTransform" do
|
|||
|
||||
it "can flip hash" do
|
||||
expect(TextTransform.flip_hash({
|
||||
"message" => "Hello",
|
||||
"name" => "Aaron",
|
||||
"handle" => "@tenderlove"
|
||||
message: "Hello",
|
||||
name: "Aaron",
|
||||
handle: "@tenderlove"
|
||||
})).to eq({
|
||||
"ollǝH" => "ǝƃɐssǝɯ",
|
||||
"uoɹɐ∀" => "ǝɯɐu",
|
||||
"ǝʌolɹǝpuǝʇ@" => "ǝlpuɐɥ"
|
||||
"ollǝH": "ǝƃɐssǝɯ",
|
||||
"uoɹɐ∀": "ǝɯɐu",
|
||||
"ǝʌolɹǝpuǝʇ@": "ǝlpuɐɥ"
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
extern crate helix;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use helix::Symbol;
|
||||
|
||||
ruby! {
|
||||
class TextTransform {
|
||||
|
@ -30,8 +31,8 @@ ruby! {
|
|||
text.into_iter().map(TextTransform::widen).collect()
|
||||
}
|
||||
|
||||
def widen_hash(text: HashMap<String, String>) -> HashMap<String, String> {
|
||||
text.into_iter().map(|(k,v)| (TextTransform::widen(k), TextTransform::widen(v))).collect()
|
||||
def widen_hash(text: HashMap<Symbol, String>) -> HashMap<Symbol, String> {
|
||||
text.into_iter().map(|(k,v)| (Symbol::from_string(TextTransform::widen(k.to_string())), TextTransform::widen(v))).collect()
|
||||
}
|
||||
|
||||
def narrowen(text: String) -> String {
|
||||
|
@ -57,8 +58,8 @@ ruby! {
|
|||
text.into_iter().map(TextTransform::narrowen).collect()
|
||||
}
|
||||
|
||||
def narrowen_hash(text: HashMap<String, String>) -> HashMap<String, String> {
|
||||
text.into_iter().map(|(k,v)| (TextTransform::narrowen(k), TextTransform::narrowen(v))).collect()
|
||||
def narrowen_hash(text: HashMap<Symbol, String>) -> HashMap<Symbol, String> {
|
||||
text.into_iter().map(|(k,v)| (Symbol::from_string(TextTransform::narrowen(k.to_string())), TextTransform::narrowen(v))).collect()
|
||||
}
|
||||
|
||||
def flip(text: String) -> String {
|
||||
|
@ -83,8 +84,8 @@ ruby! {
|
|||
text.into_iter().rev().map(TextTransform::flip).collect()
|
||||
}
|
||||
|
||||
def flip_hash(text: HashMap<String, String>) -> HashMap<String, String> {
|
||||
text.into_iter().map(|(k,v)| (TextTransform::flip(v), TextTransform::flip(k))).collect()
|
||||
def flip_hash(text: HashMap<Symbol, String>) -> HashMap<Symbol, String> {
|
||||
text.into_iter().map(|(k,v)| (Symbol::from_string(TextTransform::flip(v)), TextTransform::flip(k.to_string()))).collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ mod unit;
|
|||
mod bool;
|
||||
mod integers;
|
||||
mod float;
|
||||
mod symbol;
|
||||
mod string;
|
||||
mod option;
|
||||
mod result;
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
use sys::{VALUE, RB_TYPE_P, T_SYMBOL, rb_sym2id, rb_id2sym};
|
||||
use super::{FromRuby, CheckedValue, CheckResult, ToRuby, ToRubyResult};
|
||||
use super::super::{Symbol};
|
||||
|
||||
impl FromRuby for Symbol {
|
||||
type Checked = CheckedValue<Symbol>;
|
||||
|
||||
fn from_ruby(value: VALUE) -> CheckResult<CheckedValue<Symbol>> {
|
||||
if unsafe { RB_TYPE_P(value, T_SYMBOL) } {
|
||||
unsafe { Ok(CheckedValue::new(value)) }
|
||||
} else {
|
||||
type_error!(value, "a symbol")
|
||||
}
|
||||
}
|
||||
|
||||
fn from_checked(checked: CheckedValue<Symbol>) -> Symbol {
|
||||
Symbol::from_id(unsafe { rb_sym2id(checked.to_value()) })
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRuby for Symbol {
|
||||
fn to_ruby(self) -> ToRubyResult {
|
||||
Ok(unsafe { rb_id2sym(self.to_id()) })
|
||||
}
|
||||
}
|
35
src/lib.rs
35
src/lib.rs
|
@ -15,7 +15,7 @@ pub extern crate libcruby_sys as sys;
|
|||
// pub use rb;
|
||||
|
||||
use std::ffi::CStr;
|
||||
use sys::VALUE;
|
||||
use sys::{VALUE, ID};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! raise {
|
||||
|
@ -54,10 +54,39 @@ mod macros;
|
|||
pub use coercions::*;
|
||||
pub use errors::*;
|
||||
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)]
|
||||
pub struct Symbol(ID);
|
||||
|
||||
// Since the main use case for `Symbol` at the moment is as the
|
||||
// key for a `HashMap`, this tries to avoid GC issues by alaways
|
||||
// pinning the symbol, essentially making it a "copy type". This
|
||||
// is probably overly aggressive, we can reconsider this when we
|
||||
// have a more general-purpose mechanism to encode pinning
|
||||
// semantics in the type system.
|
||||
impl Symbol {
|
||||
pub fn from_id(id: ID) -> Symbol {
|
||||
Symbol(id)
|
||||
}
|
||||
|
||||
pub fn to_id(self) -> ID {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn from_string(string: String) -> Symbol {
|
||||
Symbol(unsafe { sys::rb_intern_str(string.to_ruby().unwrap()) })
|
||||
}
|
||||
|
||||
pub fn to_string(self) -> String {
|
||||
unsafe { String::from_ruby_unwrap(sys::rb_id2str(self.0)) }
|
||||
}
|
||||
}
|
||||
|
||||
pub use class_definition::{ClassDefinition, MethodDefinition};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub struct Class(VALUE);
|
||||
|
||||
pub trait RubyMethod {
|
||||
|
@ -101,7 +130,7 @@ impl Class {
|
|||
Class(value)
|
||||
}
|
||||
|
||||
pub fn to_value(&self) -> VALUE {
|
||||
pub fn to_value(self) -> VALUE {
|
||||
self.0
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue