d-unsed / ruru

Native Ruby extensions written in Rust
MIT License
834 stars 40 forks source link

Need for unwraps everywhere? #35

Closed NullVoxPopuli closed 8 years ago

NullVoxPopuli commented 8 years ago

I feel like I'm needing to unwrap() everything to pass to other functions.

Here is what I have so far

#[macro_use]
extern crate ruru;
extern crate inflector;

// // dash: kebab-case
use inflector::cases::kebabcase::to_kebab_case;
// // underscore: snake_case
use inflector::cases::snakecase::to_snake_case;
// // camel_lower: camelCase
use inflector::cases::camelcase::to_camel_case;
// // camel: ClassCase (PascalCase)
use inflector::cases::classcase::to_class_case;

use ruru::{Class, Object, RString, Hash, Array, Symbol, AnyObject, VM};
use ruru::types::ValueType;

class!(CaseTransform);

methods! (
    CaseTransform,
    itself,

    fn deepTransformKeys(hash: Hash, block: &Fn(String) -> String) -> Hash {
        let result = Hash::new();

        hash.unwrap().each(|key, value| {
            let newValue = if value.ty() == ValueType::Hash { deepTransformKeys(value, block).to_any_object() } else { value };
            let newKey = RString::new(block(key.unwrap().to_string()));
            result.store(newKey, newValue);
        });

        result
    }

    fn transformArray(value: Array, transformMethod: &Fn(AnyObject) -> AnyObject) -> Array {
        value.map(|item| transformMethod(item)).unwrap()
    }

    fn transformHash(value: Hash, transformMethod: &Fn(AnyObject) -> AnyObject) -> Hash {
        deepTransformKeys(value, |key| transformMethod(key))
    }

    fn transformSymbol(value: Symbol, transformMethod: &Fn(AnyObject) -> AnyObject) -> Symbol {
        let transformed = transformMethod(value);
        Symbol::new(transformed);
    }

    fn transform(
        value: AnyObject,
        objectTransform: &Fn(AnyObject) -> AnyObject,
        keyTransform: &Fn(String) -> String
    ) -> AnyObject {
        match value.unwrap().ty() {
            ValueType::Array => transformArray(value, objectTransform).to_any_object(),
            ValueType::Hash => transformHash(value, objectTransform).to_any_object(),
            ValueType::Symbol => transformSymbol(value, objectTransform).to_any_object(),
            ValueType::RString => keyTransform(value).to_any_object(),
            ValueType::Object => value
        }
    }

    fn toPascalCase(key: String) -> String { to_class_case(to_snake_case(key.unwrap())) }
    fn toCamelCase(key: String) -> String { to_camel_case(to_snake_case(key.unwrap())) }
    fn toDashedCase(key: String) -> String { to_kebab_case(to_snake_case(key.unwrap())) }
    fn toSnakeCase(key: String) -> String { to_snake_case(key.unwrap()) }

    fn camel(value: AnyObject) -> AnyObject { transform(value.unwrap().to_any_object(), &camel, &toPascalCase) }
    fn camelLower(value: AnyObject) -> AnyObject { transform(value.unwrap().to_any_object(), &camelLower, &toCamelCase) }
    fn dash(value: AnyObject) -> AnyObject { transform(value.unwrap().to_any_object(), &dash, &toDashedCase) }
    fn underscore(value: AnyObject) -> AnyObject { transform(value.unwrap(), &underscore, &toSnakeCase) }
    fn unaltered(value: AnyObject) -> AnyObject { value.unwrap().to_any_object() }
);

#[no_mangle]
pub extern fn initialize_case_transform() {
    Class::new("CaseTransform", None).define(|itself| {
        itself.def_self("camel", camel);
        itself.def_self("camel_lower", camelLower);
        itself.def_self("dash", dash);
        itself.def_self("underscore", underscore);
        itself.def_self("unaltered", unaltered);
    });
}

I may also be struggling with Rust as I'm still fairly new to it -- and I may be trying too hard to adapt the ruby implementation I had of this to Rust (hence all the AnyObject / to_any_object everywhere). :-\

Here is the full repo, if you're interested: https://github.com/NullVoxPopuli/case_transform/blob/8f017c642a254388d6ca6b4c2aaee8f10fea0d17/ext/case_transform/src/lib.rs

d-unsed commented 8 years ago

Hey @NullVoxPopuli!

I'll get to this in a couple of hours and explain how to do implement it the rust-way :)

NullVoxPopuli commented 8 years ago

Thanks for that! (You're pretty much my hero right now) :-)

Here is the ruby version in case intentions are unclear: https://github.com/NullVoxPopuli/case_transform/blob/77b154c62020d61d55a3186b73662bf105e84aac/lib/case_transform.rb

d-unsed commented 8 years ago
  1. methods! macro should be used to define only those methods which are exposed to Ruby (those ones which are used in def_self in your example). You should move everything else out of the macro.

    Long story short, Ruby methods and Rust functions are different and those functions which are declared inside methods! macro are transformed during compile-time to be compatible with MRI.

    So answering the title of the issue - no. Once you unwrap an object in a ruby method and pass it to Rust function, you won't need to unwrap it again.

  2. As I see, you've tried to rewrite the Rust implementation the same way as the one in Ruby.

    The code can be much simplier. Rust has a powerful type system and in ruru I try to reuse it for Ruby objects.

  3. A small note, Rust has the same conventions on variable and function naming as Ruby - snake_case.

I will submit a PR to case_transform, because the code can tell much more than me :)

NullVoxPopuli commented 8 years ago

I guess I'm adding you to the authors list in my gemspec :-)

thanks a ton!

Rust looks really cool so far. Just different. haha

d-unsed commented 8 years ago

I'll close this issue. If you have more questions, please ask them in the PR comments.

NullVoxPopuli commented 8 years ago

cool beans