Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[please do not merge] Rewrite one method in rust #787

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft

Conversation

sandbergja
Copy link
Member

@sandbergja sandbergja commented Jun 11, 2024

With much thanks to this blog post:
https://blog.codeminer42.com/integrating-ruby-with-rust-with-ffi/

In order to deploy this, we would first need to add cargo to the lib-jobs boxes (perhaps through apt).

Currently, the code is in a crate of type cdylib, which does not support doctests! 😢 I wonder if we could use the cdylib as a compatibility layer, and then use a lib crate (with doctests) to actually provide the functionality, with doctests.

We should also make sure we are running the following regularly, perhaps as part of CI?

  • cargo clippy
  • cargo test
  • cargo fmt
  • cargo doc

The original blog post said that we need to free the memory after we are done, using the pointer we get from calling the ffi function in the second element in the array. But when I try that, I get "calling free on non allocated pointer", and the ffi gem documentation says that the garbage collecter will do this for us... I hope this is true, it would be ironic to add memory leaks when using two memory safe languages 😬

With much thanks to this blog post:
https://blog.codeminer42.com/integrating-ruby-with-rust-with-ffi/

In order to deploy this, we would first need to add cargo to the
lib-jobs boxes (perhaps through apt).

Currently, the code is in a crate of type cdylib, which does not
support doctests! 😢 I wonder if we could use the cdylib as a
compatibility layer, and then use a lib crate (with doctests)
to actually provide the functionality.

We should also make sure we are running the following regularly,
perhaps as part of CI?
 * `cargo clippy`
 * `cargo test`
 * `cargo fmt`
 * `cargo doc`

I am also confused as to whether or not this contains a memory
leak.  FFI is returning a two-element array, with our string and
then a pointer.  But if I try to free the pointer afterwards
(which I could be doing wrong), I get an error that it hasn't
been allocated?  Very mysterious (and ironic that we have to
worry about it when using memory safe languages like Ruby and Rust).
@@ -5,6 +5,11 @@ module LcCallSlips
# Marc::DataField for the keywords that a selector
# is interested in.
class KeywordField
extend FFI::Library
c_lib_extension = /darwin/.match?(RUBY_PLATFORM) ? 'dylib' : 'so'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Annoyingly, Mac and Linux use different file extension for dynamic libraries.

extend FFI::Library
c_lib_extension = /darwin/.match?(RUBY_PLATFORM) ? 'dylib' : 'so'
ffi_lib Rails.root.join('lib_jobs_rs', 'target', 'release', "liblib_jobs.#{c_lib_extension}")
attach_function :normalize_keyword, [:string], :strptr
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

normalize_keyword is the name of our rust function, and we specify that it takes a string and returns a string pointer.

@@ -41,7 +46,7 @@ def word_is_keyword?(word)
end

def normalize(word)
word.sub(/[[:punct:]]?$/, '')
normalize_keyword(word)[0]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of the original Ruby implementation, use the normalize_keyword function that we attached earlier.

@@ -0,0 +1,4 @@
# frozen_string_literal: true
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compile the rust code each time we start rails or run the tests

/// there are some safety considerations described in
/// <https://doc.rust-lang.org/std/ffi/struct.CStr.html#method.from_ptr>
#[no_mangle]
pub unsafe extern "C" fn normalize_keyword(raw_string_ptr: *const i8) -> *const i8 {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is mainly glue code: taking the pointer that ruby provided, calling the logic of our function, and then returning a new pointer to the corrected string.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need the unsafe keyword in the function signature since the only unsafe call is wrapped in an unsafe block. We don't need to use an unsafe block in the test if this function isn't marked as unsafe.

.into_raw()
}

fn normalize_string(original_string: &str) -> &str {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actual Rust implementation of the original Ruby method

Co-authored-by: Ryan Laddusaw <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants