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(...);
|
// pub type c_func = extern "C" fn(...);
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)]
|
||||||
pub struct ID(*mut void);
|
pub struct ID(*mut void);
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
@ -30,7 +30,7 @@ pub struct ID(*mut void);
|
||||||
pub struct VALUE(*mut void);
|
pub struct VALUE(*mut void);
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||||
pub struct RubyException(isize);
|
pub struct RubyException(isize);
|
||||||
|
|
||||||
impl RubyException {
|
impl RubyException {
|
||||||
|
@ -163,6 +163,9 @@ extern "C" {
|
||||||
#[link_name = "HELIX_T_FALSE"]
|
#[link_name = "HELIX_T_FALSE"]
|
||||||
pub static T_FALSE: isize;
|
pub static T_FALSE: isize;
|
||||||
|
|
||||||
|
#[link_name = "HELIX_T_SYMBOL"]
|
||||||
|
pub static T_SYMBOL: isize;
|
||||||
|
|
||||||
#[link_name = "HELIX_T_FIXNUM"]
|
#[link_name = "HELIX_T_FIXNUM"]
|
||||||
pub static T_FIXNUM: isize;
|
pub static T_FIXNUM: isize;
|
||||||
|
|
||||||
|
@ -188,6 +191,10 @@ extern "C" {
|
||||||
pub fn rb_sprintf(specifier: c_string, ...) -> VALUE;
|
pub fn rb_sprintf(specifier: c_string, ...) -> VALUE;
|
||||||
pub fn rb_inspect(value: VALUE) -> VALUE;
|
pub fn rb_inspect(value: VALUE) -> VALUE;
|
||||||
pub fn rb_intern(string: c_string) -> ID;
|
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_new_capa(capa: isize) -> VALUE;
|
||||||
pub fn rb_ary_entry(ary: VALUE, offset: isize) -> VALUE;
|
pub fn rb_ary_entry(ary: VALUE, offset: isize) -> VALUE;
|
||||||
pub fn rb_ary_push(ary: VALUE, item: VALUE) -> VALUE;
|
pub fn rb_ary_push(ary: VALUE, item: VALUE) -> VALUE;
|
||||||
|
|
|
@ -11,13 +11,13 @@ describe "TextTransform" do
|
||||||
|
|
||||||
it "can widen hash" do
|
it "can widen hash" do
|
||||||
expect(TextTransform.widen_hash({
|
expect(TextTransform.widen_hash({
|
||||||
"message" => "Hello",
|
message: "Hello",
|
||||||
"name" => "Aaron",
|
name: "Aaron",
|
||||||
"handle" => "@tenderlove"
|
handle: "@tenderlove"
|
||||||
})).to eq({
|
})).to eq({
|
||||||
"message" => "Hello",
|
"message": "Hello",
|
||||||
"name" => "Aaron",
|
"name": "Aaron",
|
||||||
"handle" => "@tenderlove"
|
"handle": "@tenderlove"
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -31,13 +31,13 @@ describe "TextTransform" do
|
||||||
|
|
||||||
it "can narrowen hash" do
|
it "can narrowen hash" do
|
||||||
expect(TextTransform.narrowen_hash({
|
expect(TextTransform.narrowen_hash({
|
||||||
"message" => "Hello",
|
"message": "Hello",
|
||||||
"name" => "Aaron",
|
"name": "Aaron",
|
||||||
"handle" => "@tenderlove"
|
"handle": "@tenderlove"
|
||||||
})).to eq({
|
})).to eq({
|
||||||
"message" => "Hello",
|
message: "Hello",
|
||||||
"name" => "Aaron",
|
name: "Aaron",
|
||||||
"handle" => "@tenderlove"
|
handle: "@tenderlove"
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -51,13 +51,13 @@ describe "TextTransform" do
|
||||||
|
|
||||||
it "can flip hash" do
|
it "can flip hash" do
|
||||||
expect(TextTransform.flip_hash({
|
expect(TextTransform.flip_hash({
|
||||||
"message" => "Hello",
|
message: "Hello",
|
||||||
"name" => "Aaron",
|
name: "Aaron",
|
||||||
"handle" => "@tenderlove"
|
handle: "@tenderlove"
|
||||||
})).to eq({
|
})).to eq({
|
||||||
"ollǝH" => "ǝƃɐssǝɯ",
|
"ollǝH": "ǝƃɐssǝɯ",
|
||||||
"uoɹɐ∀" => "ǝɯɐu",
|
"uoɹɐ∀": "ǝɯɐu",
|
||||||
"ǝʌolɹǝpuǝʇ@" => "ǝlpuɐɥ"
|
"ǝʌolɹǝpuǝʇ@": "ǝlpuɐɥ"
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
extern crate helix;
|
extern crate helix;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use helix::Symbol;
|
||||||
|
|
||||||
ruby! {
|
ruby! {
|
||||||
class TextTransform {
|
class TextTransform {
|
||||||
|
@ -30,8 +31,8 @@ ruby! {
|
||||||
text.into_iter().map(TextTransform::widen).collect()
|
text.into_iter().map(TextTransform::widen).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
def widen_hash(text: HashMap<String, String>) -> HashMap<String, String> {
|
def widen_hash(text: HashMap<Symbol, String>) -> HashMap<Symbol, String> {
|
||||||
text.into_iter().map(|(k,v)| (TextTransform::widen(k), TextTransform::widen(v))).collect()
|
text.into_iter().map(|(k,v)| (Symbol::from_string(TextTransform::widen(k.to_string())), TextTransform::widen(v))).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
def narrowen(text: String) -> String {
|
def narrowen(text: String) -> String {
|
||||||
|
@ -57,8 +58,8 @@ ruby! {
|
||||||
text.into_iter().map(TextTransform::narrowen).collect()
|
text.into_iter().map(TextTransform::narrowen).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
def narrowen_hash(text: HashMap<String, String>) -> HashMap<String, String> {
|
def narrowen_hash(text: HashMap<Symbol, String>) -> HashMap<Symbol, String> {
|
||||||
text.into_iter().map(|(k,v)| (TextTransform::narrowen(k), TextTransform::narrowen(v))).collect()
|
text.into_iter().map(|(k,v)| (Symbol::from_string(TextTransform::narrowen(k.to_string())), TextTransform::narrowen(v))).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
def flip(text: String) -> String {
|
def flip(text: String) -> String {
|
||||||
|
@ -83,8 +84,8 @@ ruby! {
|
||||||
text.into_iter().rev().map(TextTransform::flip).collect()
|
text.into_iter().rev().map(TextTransform::flip).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
def flip_hash(text: HashMap<String, String>) -> HashMap<String, String> {
|
def flip_hash(text: HashMap<Symbol, String>) -> HashMap<Symbol, String> {
|
||||||
text.into_iter().map(|(k,v)| (TextTransform::flip(v), TextTransform::flip(k))).collect()
|
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 bool;
|
||||||
mod integers;
|
mod integers;
|
||||||
mod float;
|
mod float;
|
||||||
|
mod symbol;
|
||||||
mod string;
|
mod string;
|
||||||
mod option;
|
mod option;
|
||||||
mod result;
|
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;
|
// pub use rb;
|
||||||
|
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use sys::VALUE;
|
use sys::{VALUE, ID};
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! raise {
|
macro_rules! raise {
|
||||||
|
@ -54,10 +54,39 @@ mod macros;
|
||||||
pub use coercions::*;
|
pub use coercions::*;
|
||||||
pub use errors::*;
|
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};
|
pub use class_definition::{ClassDefinition, MethodDefinition};
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||||
pub struct Class(VALUE);
|
pub struct Class(VALUE);
|
||||||
|
|
||||||
pub trait RubyMethod {
|
pub trait RubyMethod {
|
||||||
|
@ -101,7 +130,7 @@ impl Class {
|
||||||
Class(value)
|
Class(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_value(&self) -> VALUE {
|
pub fn to_value(self) -> VALUE {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue