-
Notifications
You must be signed in to change notification settings - Fork 286
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
Support for bigint
#861
Support for bigint
#861
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zone117x Thanks so much for this amazing contribution! I appreciate the thought that went into it! I have left some comments.
Additionally, you can remove neon-sys
/ legacy
code. This backend is no longer supported and we can set some feature flags to avoid it being compiled.
I think my main question is what the API should look like. Is it better to have separate constructors for each supported from_
type or is it better to have a trait
and a single generic constructor? I think I favor being generic. What do you think?
|
||
fn create_bigint_words( | ||
env: Env, | ||
sign_bit: isize, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a std::os::raw::c_int
or i32
.
env: Env, | ||
sign_bit: isize, | ||
word_count: usize, | ||
words: *mut u64, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
words: *mut u64, | |
words: *const u64, |
Also, result
is missing. I recommend using the bindings.rs
output from nodejs-sys
as a reference.
cat ./target/debug/build/nodejs-sys-*/out/bindings.rs
fn get_value_bigint_words( | ||
env: Env, | ||
value: Value, | ||
sign_bit: *mut isize, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as previous comment, c_int
or i32
.
env: Env, | ||
value: Value, | ||
sign_bit: *mut isize, | ||
words: *mut u64, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing word_count
.
/// # Safety | ||
/// | ||
/// `env` is a raw pointer. Please ensure it points to a napi_env that is valid for the current context. | ||
pub unsafe fn new_bigint(env: Env, value: i64) -> Local { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit, name this new
since it's namespaced by the bigint
module.
use neon_runtime::bigint;
bigint::new(...)
} | ||
|
||
#[derive(Debug)] | ||
pub struct GetBigIntLossyValueResult<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's fairly common in Rust to put the data in the Err
variant when it is still usable after a partial failure. For example, see LockResult
on Mutex
.
I think it would be more idiomatic to create an Error
wrapper (mostly for clear printing and ?
error propagation) that indicates data loss. If the user doesn't mind the loss, they can add .unwrap_or_else(Error::into_inner)
.
struct Error<T>(T);
impl<T> Error<T> {
fn into_inner(self) -> T {
self.0
}
}
impl<T> std::error::Error for Error {}
} | ||
} | ||
|
||
impl Into<i64> for GetBigIntLossyValueResult<i64> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typically From
is implemented instead of Into
since there is a reflexive implementation of Into
.
impl Into<i64> for GetBigIntLossyValueResult<i64> { | |
impl From<GetBigIntLossyValueResult<i64>> for i64 { | |
fn from(other: GetBigIntLossyValueResult<i64>) -> Self { | |
self.value | |
} | |
} |
} | ||
|
||
impl JsBigInt { | ||
pub fn new<'a, C: Context<'a>, T: Into<i64>>(cx: &mut C, x: T) -> Handle<'a, JsBigInt> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about making this generic over a trait? It could work well to allow providing an implementation for num-bigint
.
Nit, n
is preferable to x
for consistency with other code in Neon.
let value = x.into(); | ||
let local = unsafe { neon_runtime::bigint::new_bigint(env, value) }; | ||
let bigint = Handle::new_internal(JsBigInt(local)); | ||
bigint |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit let the type be returned directly instead of the unused variable binding.
bigint | |
Handle::new_internal(JsBigInt(local)) |
|
||
impl ValueInternal for JsBigInt { | ||
fn name() -> String { | ||
"bigint".to_string() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Match the JavaScript capitalization.
"bigint".to_string() | |
"BigInt".to_string() |
Good question, I've spent some time thinking about that myself. In my opinion, the Neon code for bigint constructors should generally match the napi code -- there are three ways to create a bigint: napi_create_bigint_int64, napi_create_bigint_uint64, and napi_create_bigint_words. The first two (construct bigint directly from a 64-bit integer) seem like a very common use-case. E.g. a lot of codebases use 64-bit ints where js What are your thoughts? |
I agree with u64 and i64 being the common use cases and making that nice. We might be able to leave off arbitrary precision until a someone brings a compelling use case. Technically all of this could be accomplished with an With that said, I don't think putting it behind a single trait and method is harmful. The implementation would be generic and specialized and shouldn't hurt performance. Did you have some example in mind where it would be harmful? The disadvantage to a single generic method is that the API is less discoverable. It requires reading the trait definition and implementations to know that you can send a |
This is awesome! I think I favor the trait approach for the constructor, too. It's mildly more future-proof for other argument types (like strings, or maybe even third-party math crates) without needing to keep growing the API surface, and it makes for a nicely clean and general single |
Idea of what a trait approach might look like: pub trait ToJsBigInt {
fn to_big_int<'cx, C: Context<'cx>(&self, cx: &mut C) -> Handle<'cx, JsBigInt>;
}
impl JsBigInt {
pub fn new<'cx, C: Context<'cx>, T: ToJsBigInt>(cx: &mut C, v: T) -> Handle<'cx, JsBigInt> {
v.to_big_int(&mut cx)
}
}
impl ToJsBigInt for u64 {
fn to_big_int<'cx, C: Context<'cx>(&self, cx: &mut C) -> Handle<'cx, JsBigInt> {
todo!()
}
}
impl ToJsBigInt for i64 {
fn to_big_int<'cx, C: Context<'cx>(&self, cx: &mut C) -> Handle<'cx, JsBigInt> {
todo!()
}
}
impl ToJsBigInt for &[u64] {
fn to_big_int<'cx, C: Context<'cx>(&self, cx: &mut C) -> Handle<'cx, JsBigInt> {
todo!()
}
}
trait Context<'cx> {
fn big_int<T: ToJsBigInt>(&mut self, v: T) -> Handle<'cx, JsBigInt> {
v.to_big_int(&mut cx)
}
} Since we can't provide multiple blanket impl for I.e., instead of |
Thanks for the feedback, I'll address the updates as soon as I have some time. @kjvalencik was this PR closed intentionally? |
@zone117x Sorry! It was closed unintentionally when the I've re-opened this PR and changed the target branch to |
@zone117x is this still something you are interested in working on? If not, I have some bandwidth to take it over--giving you credit, of course! Cheers. |
Sorry, I do not have bandwidth to continue working on this. Please feel free to take over! |
Initial support for
JsBigInt
(neon) /bigint
(js). So far implemented wrappers for creating and getting bigints usingi64
andu64
.TODO:
napi_create_bigint_words
u128
andi128
types by automatically using the above arbitrary-length APIPlease let me know if this PR is headed in the right direction. I created it against the
next/0.10
branch as I had difficulties getting themain
branch working correctly with the existing project I have to test this feature.