From 237c8d7fdfdc6a49c0e57d5f06a29ec9d766add0 Mon Sep 17 00:00:00 2001 From: Dave Herman Date: Tue, 7 Nov 2017 22:25:33 -0800 Subject: [PATCH 1/6] WIP: RFC: ArrayBuffer Views --- text/0000-array-buffer-views.md | 130 ++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 text/0000-array-buffer-views.md diff --git a/text/0000-array-buffer-views.md b/text/0000-array-buffer-views.md new file mode 100644 index 0000000..5568f92 --- /dev/null +++ b/text/0000-array-buffer-views.md @@ -0,0 +1,130 @@ +- Feature Name: array_buffer_views +- Start Date: 2017-11-07 +- RFC PR: +- Neon Issue: + +# Summary +[summary]: #summary + +This RFC proposes changing `JsArrayBuffer`'s implementation of [vm::Lock](https://api.neon-bindings.com/neon/vm/trait.lock) to expose a zero-cost `ArrayBufferView` type, which gives access to the underlying buffer data with all the view types of JavaScript's [typed arrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays). + +# Motivation +[motivation]: #motivation + +The `JsArrayBuffer` API currently gives direct access to a `CMutSlice`, but if you want to operate on the data at different primitive types, you have to use unsafe code to transmute the slice. Any time Neon users are tempted to use unsafe code we should make sure there are easier safe APIs available for them to use instead. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +The Rust type for JavaScript [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)s is [`JsArrayBuffer`](https://api.neon-bindings.com/neon/js/binary/struct.jsarraybuffer). + +A `JsArrayBuffer` provides access to its internal buffer via the [`Lock::grab`](https://api.neon-bindings.com/neon/vm/trait.lock#method.grab) method. By calling the `grab` method with a callback, your code is given access to an `ArrayBufferView` struct. You can use this struct to get views over the buffer with different typed formats. For example, as a `u32` slice: + +```rust +let ab = x.check::()?; +ab.grab(|mut contents| { + let mut ints = contents.as_u32_slice(); + ints[0] = 17; + ints[1] = 42; +}) +``` + +Or an `f64` slice: + +```rust +let ab = x.check::()?; +ab.grab(|mut contents| { + let mut floats = contents.as_f64_slice(); + floats[0] = 1.23; + floats[1] = 3.14; +}) +``` + +You can also extract differently-typed slices in separate scopes: + +```rust +let ab = x.check::()?; +ab.grab(|mut contents| { + { + let mut ints = contents.as_u32_slice(); + ints[0] = 17; + ints[1] = 42; + } + { + let mut floats = contents.as_f64_slice(); + floats[1] = 3.14; + } +}); +``` + + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +## The `ArrayBufferView` type + +The primary change to `JsArrayBuffer` is in its implementation of the `Lock` trait. Instead of defining the associated type `Internals` directly as `CMutSlice`, we change it to a newly-defined `neon::js::binary::ArrayBufferView` struct type: + +```rust +struct ArrayBufferView; + +impl ArrayBufferView { + fn as_slice(&mut self) -> CMutSlice; + fn len(&self) -> usize; +} +``` + +## The `ViewType` trait + +The `ViewType` trait is defined as `unsafe` since its alignment and size must be computed correctly to stay within the bounds of the buffer data. + +```rust +unsafe trait ViewType; +``` + +The types that implement `ViewType` by default are the same as JavaScript typed array element types. This proposal also adds support for 64-bit integers since, unlike in JavaScript, they provide no particular challenge for Rust. + +- `u8` +- `i8` +- `u16` +- `i16` +- `u32` +- `i32` +- `u64` +- `i64` +- `f32` +- `f64` + +## Convenience methods + +Some contexts of use of `as_slice()` may not provide enough information to Rust's type inference algorithm to determine the `ViewType`, leading to potentially confusing errors. Especially for teaching material and for making this more accessible to new Rust programmers, this proposal also includes convenience methods that are fixed to a specific type. + +```rust +impl ArrayBufferView { + fn as_u8_slice(&mut self) -> CMutSlice; + fn as_i8_slice(&mut self) -> CMutSlice; + fn as_u16_slice(&mut self) -> CMutSlice; + fn as_i16_slice(&mut self) -> CMutSlice; + fn as_u32_slice(&mut self) -> CMutSlice; + fn as_i32_slice(&mut self) -> CMutSlice; + fn as_u64_slice(&mut self) -> CMutSlice; + fn as_i64_slice(&mut self) -> CMutSlice; + fn as_f32_slice(&mut self) -> CMutSlice; + fn as_f64_slice(&mut self) -> CMutSlice; +} +``` + +# Drawbacks +[drawbacks]: #drawbacks + + +# Rationale and alternatives +[alternatives]: #alternatives + + +# Unresolved questions +[unresolved]: #unresolved-questions + +Should we include methods for splitting an `ArrayBufferView` at a particular index, allowing distinct sub-slices to be aliased to different slice types at the same time? This seems potentially useful but probably could be separated into its own proposal. + +Is there value in also defining an API for working with typed arrays? It seems less crucial but maybe avoids unnecessary allocations. At any rate it seems like it could be presented orthogonally. From 3ce0a7cc17d90c842438bd67afd0902b08c6b273 Mon Sep 17 00:00:00 2001 From: Dave Herman Date: Wed, 8 Nov 2017 17:50:56 -0800 Subject: [PATCH 2/6] separate `as_slice` vs `as_mut_slice`; lots more documentation; added drawbacks and alternatives sections --- text/0000-array-buffer-views.md | 156 ++++++++++++++++++++++++++------ 1 file changed, 130 insertions(+), 26 deletions(-) diff --git a/text/0000-array-buffer-views.md b/text/0000-array-buffer-views.md index 5568f92..02ec582 100644 --- a/text/0000-array-buffer-views.md +++ b/text/0000-array-buffer-views.md @@ -16,47 +16,109 @@ The `JsArrayBuffer` API currently gives direct access to a `CMutSlice`, but # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -The Rust type for JavaScript [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)s is [`JsArrayBuffer`](https://api.neon-bindings.com/neon/js/binary/struct.jsarraybuffer). +The Rust type for JavaScript [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)s is [`JsArrayBuffer`](https://api.neon-bindings.com/neon/js/binary/struct.jsarraybuffer): + +```rust +let buffer: Handle = x.check::()?; +``` + +## Reading buffer data A `JsArrayBuffer` provides access to its internal buffer via the [`Lock::grab`](https://api.neon-bindings.com/neon/vm/trait.lock#method.grab) method. By calling the `grab` method with a callback, your code is given access to an `ArrayBufferView` struct. You can use this struct to get views over the buffer with different typed formats. For example, as a `u32` slice: ```rust -let ab = x.check::()?; -ab.grab(|mut contents| { - let mut ints = contents.as_u32_slice(); +let first: u32 = buffer.grab(|contents| { + contents.as_u32_slice()[0] +}); +``` + +or an `f64` slice: + +```rust +let first: f64 = buffer.grab(|contents| { + contents.as_f64_slice()[0] +}); +``` + +You can view the same buffer with multiple different view types: + +```rust +let (first: u32, second: u32, third: f64) = buffer.grab(|contents| { + let ints = contents.as_u32_slice(); + let floats = contents.as_f64_slice(); + (ints[0], ints[1], floats[1]) +}); +``` + +Notice how in the last example, the first two `u32` values are indexed at 32-bit offsets, and the final `f64` value is indexed at a 64-bit offset. That is, if the buffer data looks like: + +``` ++-----+-----+-----------+ +| u32 | u32 | f64 | ++-----+-----+-----------+ +0 4 8 +``` + +then the first two cells are located at byte offsets 0 and 4 respectively and `u32` offsets 0 and 1 respectively, and the third cell is located at byte offset 8 and `f64` offset 1. + +## Writing to buffer data + +The `as_mut_XXX_slice()` methods provide mutable access to a buffer's cata. + +```rust +buffer.grab(|mut contents| { + let mut ints = contents.as_mut_u32_slice(); ints[0] = 17; ints[1] = 42; -}) +}); ``` -Or an `f64` slice: +Notice how the callback's `contents` parameter is annotated as `mut` in order to allow mutable access to the data. + +Once again, here is an example of an `f64` slice: ```rust -let ab = x.check::()?; -ab.grab(|mut contents| { - let mut floats = contents.as_f64_slice(); +buffer.grab(|mut contents| { + let mut floats = contents.as_mut_f64_slice(); floats[0] = 1.23; floats[1] = 3.14; -}) +}); ``` -You can also extract differently-typed slices in separate scopes: +You can also extract differently-typed mutable slices, but as always with mutable references, they cannot coexist at the same time. So you have to create them in separate scopes: ```rust -let ab = x.check::()?; -ab.grab(|mut contents| { +buffer.grab(|mut contents| { { - let mut ints = contents.as_u32_slice(); + let mut ints = contents.as_mut_u32_slice(); ints[0] = 17; ints[1] = 42; } { - let mut floats = contents.as_f64_slice(); + let mut floats = contents.as_mut_f64_slice(); floats[1] = 3.14; } }); ``` +## Generic version + +If you prefer, you can instead use the generic `as_slice` and `as_mut_slice` versions of the API. These leads to more concise code, which can be nice when the type is clear from context: + +```rust +let first: u32 = buffer.grab(|contents| { + contents.as_slice()[0] +}); +``` + +Here, because of the type annotation on `first`, the `as_slice` method is inferred to produce a `CSlice`. + +### Custom view types + +The `as_slice` and `as_mut_slice` methods produce slices of any type that implements the `ViewType` trait. By default, all of the primitive types corresponding to JavaScript typed array view types implement this trait, in addition to `u64` and `i64`. (JavaScript doesn't provide typed arrays for 64-bit integers since they aren't expressible as JavaScript primitive values.) + +This also makes it possible to create custom `ViewType` implementations for custom view types, but these must be implemented with `unsafe` code. + # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -69,11 +131,18 @@ The primary change to `JsArrayBuffer` is in its implementation of the `Lock` tra struct ArrayBufferView; impl ArrayBufferView { - fn as_slice(&mut self) -> CMutSlice; + fn as_slice(&self) -> CSlice; + fn as_mut_slice(&mut self) -> CMutSlice; fn len(&self) -> usize; } ``` +The `as_slice` method produces a read-only slice of the buffer data. + +The `as_mut_slice` method produces a mutable slice of the buffer data. + +The `len` method produces the byte length of the buffer. + ## The `ViewType` trait The `ViewType` trait is defined as `unsafe` since its alignment and size must be computed correctly to stay within the bounds of the buffer data. @@ -82,6 +151,8 @@ The `ViewType` trait is defined as `unsafe` since its alignment and size must be unsafe trait ViewType; ``` +### Built-in `ViewType` implementations + The types that implement `ViewType` by default are the same as JavaScript typed array element types. This proposal also adds support for 64-bit integers since, unlike in JavaScript, they provide no particular challenge for Rust. - `u8` @@ -95,32 +166,57 @@ The types that implement `ViewType` by default are the same as JavaScript typed - `f32` - `f64` +### Custom `ViewType` implementations + +While it requires `unsafe` code, this design allows users to define their own `ViewTypes` for compound types such as tuples or structs. + ## Convenience methods Some contexts of use of `as_slice()` may not provide enough information to Rust's type inference algorithm to determine the `ViewType`, leading to potentially confusing errors. Especially for teaching material and for making this more accessible to new Rust programmers, this proposal also includes convenience methods that are fixed to a specific type. ```rust impl ArrayBufferView { - fn as_u8_slice(&mut self) -> CMutSlice; - fn as_i8_slice(&mut self) -> CMutSlice; - fn as_u16_slice(&mut self) -> CMutSlice; - fn as_i16_slice(&mut self) -> CMutSlice; - fn as_u32_slice(&mut self) -> CMutSlice; - fn as_i32_slice(&mut self) -> CMutSlice; - fn as_u64_slice(&mut self) -> CMutSlice; - fn as_i64_slice(&mut self) -> CMutSlice; - fn as_f32_slice(&mut self) -> CMutSlice; - fn as_f64_slice(&mut self) -> CMutSlice; + fn as_u8_slice(&self) -> CSlice; + fn as_mut_u8_slice(&mut self) -> CMutSlice; + + fn as_i8_slice(&self) -> CSlice; + fn as_mut_i8_slice(&mut self) -> CMutSlice; + + fn as_u16_slice(&self) -> CSlice; + fn as_mut_u16_slice(&mut self) -> CMutSlice; + + fn as_i16_slice(&self) -> CSlice; + fn as_mut_i16_slice(&mut self) -> CMutSlice; + + fn as_u32_slice(&self) -> CSlice; + fn as_mut_u32_slice(&mut self) -> CMutSlice; + + fn as_i32_slice(&self) -> CSlice; + fn as_mut_i32_slice(&mut self) -> CMutSlice; + + fn as_u64_slice(&self) -> CSlice; + fn as_mut_u64_slice(&mut self) -> CMutSlice; + + fn as_i64_slice(&self) -> CSlice; + fn as_mut_i64_slice(&mut self) -> CMutSlice; + + fn as_f32_slice(&self) -> CSlice; + fn as_mut_f32_slice(&mut self) -> CMutSlice; + + fn as_f64_slice(&self) -> CSlice; + fn as_mut_f64_slice(&mut self) -> CMutSlice; } ``` # Drawbacks [drawbacks]: #drawbacks +It's annoying that multiple mutable views have to be in separate scopes. This might be something that would be relaxed by non-lexical lifetimes, which is coming pretty soon to Rust. And generally I don't know if there's any way to do better; it would violate the core semantics of Rust references to allow aliasing. # Rationale and alternatives [alternatives]: #alternatives +An alternative approach would be to use JavaScript typed array views to determine the type of the Rust slice. But this would require allocating different JS objects and passing values back and forth between Rust and JS. All Rust really needs is the backing store; by contrast, the typed array view objects only exist because they are a dynamic language's approach to aliasing views over a backing store. So this design prefers to focus on the underlying `ArrayBuffer` and let Rust operate on its data through different Rust view types. # Unresolved questions [unresolved]: #unresolved-questions @@ -128,3 +224,11 @@ impl ArrayBufferView { Should we include methods for splitting an `ArrayBufferView` at a particular index, allowing distinct sub-slices to be aliased to different slice types at the same time? This seems potentially useful but probably could be separated into its own proposal. Is there value in also defining an API for working with typed arrays? It seems less crucial but maybe avoids unnecessary allocations. At any rate it seems like it could be presented orthogonally. + +Should we ensure that multibyte data is [always little-endian](http://calculist.org/blog/2012/04/25/the-little-endian-web/), regardless of the architecture? I think the answer is probably yes for maximum portability. Maybe we could offer a less convenient variant that is platform-specific, like `struct u32_sys_endian(u32)` for the uncommon case of non-portable system-endianness? + +Are there any security or portability issues around encodings of NaN or subnormals? + +What are the detailed requirements of the `ViewType` trait? + +We should also be able to consume and produce typed array objects, even though this API focuses on the `ArrayBuffer` so far. That should be a lighter-weight layer on top of the one described here. This should be added to this RFC. From adbcdafa85cb8d61e7f77e581608eaf75e9717f5 Mon Sep 17 00:00:00 2001 From: Dave Herman Date: Wed, 8 Nov 2017 17:53:20 -0800 Subject: [PATCH 3/6] label byte offset 16 as well in the binary layout diagram --- text/0000-array-buffer-views.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-array-buffer-views.md b/text/0000-array-buffer-views.md index 02ec582..ef5a570 100644 --- a/text/0000-array-buffer-views.md +++ b/text/0000-array-buffer-views.md @@ -56,7 +56,7 @@ Notice how in the last example, the first two `u32` values are indexed at 32-bit +-----+-----+-----------+ | u32 | u32 | f64 | +-----+-----+-----------+ -0 4 8 +0 4 8 16 ``` then the first two cells are located at byte offsets 0 and 4 respectively and `u32` offsets 0 and 1 respectively, and the third cell is located at byte offset 8 and `f64` offset 1. From 2ae7ca754af66fedef17d7325ec0bf6b83d3d1bd Mon Sep 17 00:00:00 2001 From: Dave Herman Date: Thu, 28 Dec 2017 23:33:56 -0800 Subject: [PATCH 4/6] `ArrayBufferView` methods produce native Rust slices instead of `CSlice` and `CMutSlice` --- text/0000-array-buffer-views.md | 64 ++++++++++++++++----------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/text/0000-array-buffer-views.md b/text/0000-array-buffer-views.md index ef5a570..f393c5a 100644 --- a/text/0000-array-buffer-views.md +++ b/text/0000-array-buffer-views.md @@ -13,6 +13,8 @@ This RFC proposes changing `JsArrayBuffer`'s implementation of [vm::Lock](https: The `JsArrayBuffer` API currently gives direct access to a `CMutSlice`, but if you want to operate on the data at different primitive types, you have to use unsafe code to transmute the slice. Any time Neon users are tempted to use unsafe code we should make sure there are easier safe APIs available for them to use instead. +Moreover, the use of `CMutSlice` instead of Rust's built-in slice types turns out to have been unnecessary. Giving Rust programs direct access to native Rust slices means Neon programs can make use of any abstractions that operate on Rust slices. + # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -111,7 +113,7 @@ let first: u32 = buffer.grab(|contents| { }); ``` -Here, because of the type annotation on `first`, the `as_slice` method is inferred to produce a `CSlice`. +Here, because of the type annotation on `first`, the `as_slice` method is inferred to produce an `&[u32]`. ### Custom view types @@ -131,8 +133,8 @@ The primary change to `JsArrayBuffer` is in its implementation of the `Lock` tra struct ArrayBufferView; impl ArrayBufferView { - fn as_slice(&self) -> CSlice; - fn as_mut_slice(&mut self) -> CMutSlice; + fn as_slice(&self) -> &[T]; + fn as_mut_slice(&mut self) -> &mut [T]; fn len(&self) -> usize; } ``` @@ -176,35 +178,35 @@ Some contexts of use of `as_slice()` may not provide enough information to Rust' ```rust impl ArrayBufferView { - fn as_u8_slice(&self) -> CSlice; - fn as_mut_u8_slice(&mut self) -> CMutSlice; + fn as_u8_slice(&self) -> &[u8]; + fn as_mut_u8_slice(&mut self) -> &mut [u8]; - fn as_i8_slice(&self) -> CSlice; - fn as_mut_i8_slice(&mut self) -> CMutSlice; + fn as_i8_slice(&self) -> &[i8]; + fn as_mut_i8_slice(&mut self) -> &mut [i8]; - fn as_u16_slice(&self) -> CSlice; - fn as_mut_u16_slice(&mut self) -> CMutSlice; + fn as_u16_slice(&self) -> &[u16]; + fn as_mut_u16_slice(&mut self) -> &mut [u16]; - fn as_i16_slice(&self) -> CSlice; - fn as_mut_i16_slice(&mut self) -> CMutSlice; + fn as_i16_slice(&self) -> &[i16]; + fn as_mut_i16_slice(&mut self) -> &mut [i16]; - fn as_u32_slice(&self) -> CSlice; - fn as_mut_u32_slice(&mut self) -> CMutSlice; + fn as_u32_slice(&self) -> &[u32]; + fn as_mut_u32_slice(&mut self) -> &mut [u32]; - fn as_i32_slice(&self) -> CSlice; - fn as_mut_i32_slice(&mut self) -> CMutSlice; + fn as_i32_slice(&self) -> &[i32]; + fn as_mut_i32_slice(&mut self) -> &mut [i32]; - fn as_u64_slice(&self) -> CSlice; - fn as_mut_u64_slice(&mut self) -> CMutSlice; + fn as_u64_slice(&self) -> &[u64]; + fn as_mut_u64_slice(&mut self) -> &mut [u64]; - fn as_i64_slice(&self) -> CSlice; - fn as_mut_i64_slice(&mut self) -> CMutSlice; + fn as_i64_slice(&self) -> &[i64]; + fn as_mut_i64_slice(&mut self) -> &mut [i64]; - fn as_f32_slice(&self) -> CSlice; - fn as_mut_f32_slice(&mut self) -> CMutSlice; + fn as_f32_slice(&self) -> &[f32]; + fn as_mut_f32_slice(&mut self) -> &mut [f32]; - fn as_f64_slice(&self) -> CSlice; - fn as_mut_f64_slice(&mut self) -> CMutSlice; + fn as_f64_slice(&self) -> &[f64]; + fn as_mut_f64_slice(&mut self) -> &mut [f64]; } ``` @@ -218,17 +220,13 @@ It's annoying that multiple mutable views have to be in separate scopes. This mi An alternative approach would be to use JavaScript typed array views to determine the type of the Rust slice. But this would require allocating different JS objects and passing values back and forth between Rust and JS. All Rust really needs is the backing store; by contrast, the typed array view objects only exist because they are a dynamic language's approach to aliasing views over a backing store. So this design prefers to focus on the underlying `ArrayBuffer` and let Rust operate on its data through different Rust view types. -# Unresolved questions -[unresolved]: #unresolved-questions - -Should we include methods for splitting an `ArrayBufferView` at a particular index, allowing distinct sub-slices to be aliased to different slice types at the same time? This seems potentially useful but probably could be separated into its own proposal. +By providing direct access to the buffer at various Rust slice types, we make the endianness of operations on typed arrays non-portable. An alternative approach would be to use a wrapper type that either guarantees little-endianness (translating with a slower path on big-endian systems), or requires programs to be explicit about which they are using. However, JavaScript has also made the same decision to use the native system endianness, and in practice, little-endianness seems to have taken over the world. So this should make Neon code no less portable than pure JavaScript code that operates on typed arrays, and the increasingly rare big-endian-only systems will simply suffer from occasional bugs. In short: [JavaScript has bet on the death of big-endian systems](http://calculist.org/blog/2012/04/25/the-little-endian-web/), and we are drafting off of their decision. -Is there value in also defining an API for working with typed arrays? It seems less crucial but maybe avoids unnecessary allocations. At any rate it seems like it could be presented orthogonally. +Similarly, we might also have chosen to put a protective abstraction in front of the slices to canonicalize NaN values. JavaScript engines have to do this when converting between data in the backing store and JavaScript values, but we don't have to be responsible for that. If signalling NaN values were a source of undefined behavior, we could have had a problem. Luckily, [signalling Nan is defined in Rust](https://twitter.com/gankro/status/931535748628729856) so we're safe. -Should we ensure that multibyte data is [always little-endian](http://calculist.org/blog/2012/04/25/the-little-endian-web/), regardless of the architecture? I think the answer is probably yes for maximum portability. Maybe we could offer a less convenient variant that is platform-specific, like `struct u32_sys_endian(u32)` for the uncommon case of non-portable system-endianness? +We could have added more API conveniences, including splitting views and working with various `ArrayBufferView` typed array types. We can safely leave these considerations to future RFCs, since they don't affect the design of the core API. -Are there any security or portability issues around encodings of NaN or subnormals? - -What are the detailed requirements of the `ViewType` trait? +# Unresolved questions +[unresolved]: #unresolved-questions -We should also be able to consume and produce typed array objects, even though this API focuses on the `ArrayBuffer` so far. That should be a lighter-weight layer on top of the one described here. This should be added to this RFC. +We need to determine the detailed requirements of the `ViewType` trait. This can be determined during the initial implementation work for this RFC. From 0a255bd2babede511199379a0c3d7b2aba985f24 Mon Sep 17 00:00:00 2001 From: Dave Herman Date: Thu, 28 Dec 2017 23:36:06 -0800 Subject: [PATCH 5/6] rename `ArrayBufferView` type (which is confusing since "ArrayBufferView" is the generic name used in the specs and docs for typed array types) in favor of `ArrayBufferData`, which is the name used in the actual JS spec --- text/0000-array-buffer-views.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/text/0000-array-buffer-views.md b/text/0000-array-buffer-views.md index f393c5a..7f9b109 100644 --- a/text/0000-array-buffer-views.md +++ b/text/0000-array-buffer-views.md @@ -6,7 +6,7 @@ # Summary [summary]: #summary -This RFC proposes changing `JsArrayBuffer`'s implementation of [vm::Lock](https://api.neon-bindings.com/neon/vm/trait.lock) to expose a zero-cost `ArrayBufferView` type, which gives access to the underlying buffer data with all the view types of JavaScript's [typed arrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays). +This RFC proposes changing `JsArrayBuffer`'s implementation of [vm::Lock](https://api.neon-bindings.com/neon/vm/trait.lock) to expose a zero-cost `ArrayBufferData` type, which gives access to the underlying buffer data with all the view types of JavaScript's [typed arrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays). # Motivation [motivation]: #motivation @@ -26,7 +26,7 @@ let buffer: Handle = x.check::()?; ## Reading buffer data -A `JsArrayBuffer` provides access to its internal buffer via the [`Lock::grab`](https://api.neon-bindings.com/neon/vm/trait.lock#method.grab) method. By calling the `grab` method with a callback, your code is given access to an `ArrayBufferView` struct. You can use this struct to get views over the buffer with different typed formats. For example, as a `u32` slice: +A `JsArrayBuffer` provides access to its internal buffer via the [`Lock::grab`](https://api.neon-bindings.com/neon/vm/trait.lock#method.grab) method. By calling the `grab` method with a callback, your code is given access to an `ArrayBufferData` struct. You can use this struct to get views over the buffer with different typed formats. For example, as a `u32` slice: ```rust let first: u32 = buffer.grab(|contents| { @@ -125,14 +125,14 @@ This also makes it possible to create custom `ViewType` implementations for cust # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -## The `ArrayBufferView` type +## The `ArrayBufferData` type -The primary change to `JsArrayBuffer` is in its implementation of the `Lock` trait. Instead of defining the associated type `Internals` directly as `CMutSlice`, we change it to a newly-defined `neon::js::binary::ArrayBufferView` struct type: +The primary change to `JsArrayBuffer` is in its implementation of the `Lock` trait. Instead of defining the associated type `Internals` directly as `CMutSlice`, we change it to a newly-defined `neon::js::binary::ArrayBufferData` struct type: ```rust -struct ArrayBufferView; +struct ArrayBufferData; -impl ArrayBufferView { +impl ArrayBufferData { fn as_slice(&self) -> &[T]; fn as_mut_slice(&mut self) -> &mut [T]; fn len(&self) -> usize; @@ -177,7 +177,7 @@ While it requires `unsafe` code, this design allows users to define their own `V Some contexts of use of `as_slice()` may not provide enough information to Rust's type inference algorithm to determine the `ViewType`, leading to potentially confusing errors. Especially for teaching material and for making this more accessible to new Rust programmers, this proposal also includes convenience methods that are fixed to a specific type. ```rust -impl ArrayBufferView { +impl ArrayBufferData { fn as_u8_slice(&self) -> &[u8]; fn as_mut_u8_slice(&mut self) -> &mut [u8]; @@ -224,7 +224,7 @@ By providing direct access to the buffer at various Rust slice types, we make th Similarly, we might also have chosen to put a protective abstraction in front of the slices to canonicalize NaN values. JavaScript engines have to do this when converting between data in the backing store and JavaScript values, but we don't have to be responsible for that. If signalling NaN values were a source of undefined behavior, we could have had a problem. Luckily, [signalling Nan is defined in Rust](https://twitter.com/gankro/status/931535748628729856) so we're safe. -We could have added more API conveniences, including splitting views and working with various `ArrayBufferView` typed array types. We can safely leave these considerations to future RFCs, since they don't affect the design of the core API. +We could have added more API conveniences, including splitting views and working with various typed array types. We can safely leave these considerations to future RFCs, since they don't affect the design of the core API. # Unresolved questions [unresolved]: #unresolved-questions From ce847c6981b17f77cddda0e342a1dba8cce6dd9d Mon Sep 17 00:00:00 2001 From: David Herman Date: Wed, 30 May 2018 23:49:35 -0700 Subject: [PATCH 6/6] rename `ArrayBufferData` to `BinaryData` and `ViewType` to `BinaryViewType` so they make sense for both `JsArrayBuffer` and `JsBuffer` --- text/0000-array-buffer-views.md | 76 ++++++++++++++++----------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/text/0000-array-buffer-views.md b/text/0000-array-buffer-views.md index 7f9b109..93c32c4 100644 --- a/text/0000-array-buffer-views.md +++ b/text/0000-array-buffer-views.md @@ -6,12 +6,12 @@ # Summary [summary]: #summary -This RFC proposes changing `JsArrayBuffer`'s implementation of [vm::Lock](https://api.neon-bindings.com/neon/vm/trait.lock) to expose a zero-cost `ArrayBufferData` type, which gives access to the underlying buffer data with all the view types of JavaScript's [typed arrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays). +This RFC proposes changing `JsArrayBuffer`'s implementation of [vm::Lock](https://api.neon-bindings.com/neon/vm/trait.lock) to expose a zero-cost `BinaryData` type, which gives access to the buffer's underyling data with all the view types of JavaScript's [typed arrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays). # Motivation [motivation]: #motivation -The `JsArrayBuffer` API currently gives direct access to a `CMutSlice`, but if you want to operate on the data at different primitive types, you have to use unsafe code to transmute the slice. Any time Neon users are tempted to use unsafe code we should make sure there are easier safe APIs available for them to use instead. +The `JsArrayBuffer` and `JsBuffer` APIs currently give direct access to a `CMutSlice`, but if you want to operate on the data at different primitive types, you have to use unsafe code to transmute the slice. Any time Neon users are tempted to use unsafe code we should make sure there are easier safe APIs available for them to use instead. Moreover, the use of `CMutSlice` instead of Rust's built-in slice types turns out to have been unnecessary. Giving Rust programs direct access to native Rust slices means Neon programs can make use of any abstractions that operate on Rust slices. @@ -26,28 +26,28 @@ let buffer: Handle = x.check::()?; ## Reading buffer data -A `JsArrayBuffer` provides access to its internal buffer via the [`Lock::grab`](https://api.neon-bindings.com/neon/vm/trait.lock#method.grab) method. By calling the `grab` method with a callback, your code is given access to an `ArrayBufferData` struct. You can use this struct to get views over the buffer with different typed formats. For example, as a `u32` slice: +A `JsArrayBuffer` provides access to its internal buffer via the [`Lock::grab`](https://api.neon-bindings.com/neon/vm/trait.lock#method.grab) method. By calling the `grab` method with a callback, your code is given access to a `BinaryData` struct. You can use this struct to get views over the buffer with different typed formats. For example, as a `u32` slice: ```rust -let first: u32 = buffer.grab(|contents| { - contents.as_u32_slice()[0] +let first: u32 = buffer.grab(|data| { + data.as_u32_slice()[0] }); ``` or an `f64` slice: ```rust -let first: f64 = buffer.grab(|contents| { - contents.as_f64_slice()[0] +let first: f64 = buffer.grab(|data| { + data.as_f64_slice()[0] }); ``` You can view the same buffer with multiple different view types: ```rust -let (first: u32, second: u32, third: f64) = buffer.grab(|contents| { - let ints = contents.as_u32_slice(); - let floats = contents.as_f64_slice(); +let (first: u32, second: u32, third: f64) = buffer.grab(|data| { + let ints = data.as_u32_slice(); + let floats = data.as_f64_slice(); (ints[0], ints[1], floats[1]) }); ``` @@ -65,23 +65,23 @@ then the first two cells are located at byte offsets 0 and 4 respectively and `u ## Writing to buffer data -The `as_mut_XXX_slice()` methods provide mutable access to a buffer's cata. +The `as_mut_XXX_slice()` methods provide mutable access to a buffer's data. ```rust -buffer.grab(|mut contents| { - let mut ints = contents.as_mut_u32_slice(); +buffer.grab(|mut data| { + let mut ints = data.as_mut_u32_slice(); ints[0] = 17; ints[1] = 42; }); ``` -Notice how the callback's `contents` parameter is annotated as `mut` in order to allow mutable access to the data. +Notice how the callback's `data` parameter is annotated as `mut` in order to allow mutable access to the data. Once again, here is an example of an `f64` slice: ```rust -buffer.grab(|mut contents| { - let mut floats = contents.as_mut_f64_slice(); +buffer.grab(|mut data| { + let mut floats = data.as_mut_f64_slice(); floats[0] = 1.23; floats[1] = 3.14; }); @@ -90,14 +90,14 @@ buffer.grab(|mut contents| { You can also extract differently-typed mutable slices, but as always with mutable references, they cannot coexist at the same time. So you have to create them in separate scopes: ```rust -buffer.grab(|mut contents| { +buffer.grab(|mut data| { { - let mut ints = contents.as_mut_u32_slice(); + let mut ints = data.as_mut_u32_slice(); ints[0] = 17; ints[1] = 42; } { - let mut floats = contents.as_mut_f64_slice(); + let mut floats = data.as_mut_f64_slice(); floats[1] = 3.14; } }); @@ -108,8 +108,8 @@ buffer.grab(|mut contents| { If you prefer, you can instead use the generic `as_slice` and `as_mut_slice` versions of the API. These leads to more concise code, which can be nice when the type is clear from context: ```rust -let first: u32 = buffer.grab(|contents| { - contents.as_slice()[0] +let first: u32 = buffer.grab(|data| { + data.as_slice()[0] }); ``` @@ -117,9 +117,9 @@ Here, because of the type annotation on `first`, the `as_slice` method is inferr ### Custom view types -The `as_slice` and `as_mut_slice` methods produce slices of any type that implements the `ViewType` trait. By default, all of the primitive types corresponding to JavaScript typed array view types implement this trait, in addition to `u64` and `i64`. (JavaScript doesn't provide typed arrays for 64-bit integers since they aren't expressible as JavaScript primitive values.) +The `as_slice` and `as_mut_slice` methods produce slices of any type that implements the `BinaryViewType` trait. By default, all of the primitive types corresponding to JavaScript typed array view types implement this trait, in addition to `u64` and `i64`. (JavaScript doesn't provide typed arrays for 64-bit integers since they aren't expressible as JavaScript primitive values.) -This also makes it possible to create custom `ViewType` implementations for custom view types, but these must be implemented with `unsafe` code. +This also makes it possible to create custom `BinaryViewType` implementations for custom view types, but these must be implemented with `unsafe` code. # Reference-level explanation @@ -127,14 +127,14 @@ This also makes it possible to create custom `ViewType` implementations for cust ## The `ArrayBufferData` type -The primary change to `JsArrayBuffer` is in its implementation of the `Lock` trait. Instead of defining the associated type `Internals` directly as `CMutSlice`, we change it to a newly-defined `neon::js::binary::ArrayBufferData` struct type: +The primary change to `JsArrayBuffer` is in its implementation of the `Lock` trait. Instead of defining the associated type `Internals` directly as `CMutSlice`, we change it to a newly-defined `neon::js::binary::BinaryData` struct type: ```rust -struct ArrayBufferData; +struct BinaryData; -impl ArrayBufferData { - fn as_slice(&self) -> &[T]; - fn as_mut_slice(&mut self) -> &mut [T]; +impl BinaryData { + fn as_slice(&self) -> &[T]; + fn as_mut_slice(&mut self) -> &mut [T]; fn len(&self) -> usize; } ``` @@ -145,17 +145,17 @@ The `as_mut_slice` method produces a mutable slice of the buffer data. The `len` method produces the byte length of the buffer. -## The `ViewType` trait +## The `BinaryViewType` trait -The `ViewType` trait is defined as `unsafe` since its alignment and size must be computed correctly to stay within the bounds of the buffer data. +The `BinaryViewType` trait is defined as `unsafe` since its alignment and size must be computed correctly to stay within the bounds of the buffer data. ```rust -unsafe trait ViewType; +unsafe trait BinaryViewType; ``` -### Built-in `ViewType` implementations +### Built-in `BinaryViewType` implementations -The types that implement `ViewType` by default are the same as JavaScript typed array element types. This proposal also adds support for 64-bit integers since, unlike in JavaScript, they provide no particular challenge for Rust. +The types that implement `BinaryViewType` by default are the same as JavaScript typed array element types. This proposal also adds support for 64-bit integers since, unlike in JavaScript, they provide no particular challenge for Rust. - `u8` - `i8` @@ -168,16 +168,16 @@ The types that implement `ViewType` by default are the same as JavaScript typed - `f32` - `f64` -### Custom `ViewType` implementations +### Custom `BinaryViewType` implementations -While it requires `unsafe` code, this design allows users to define their own `ViewTypes` for compound types such as tuples or structs. +While it requires `unsafe` code, this design allows users to define their own `BinaryViewType`s for compound types such as tuples or structs. ## Convenience methods -Some contexts of use of `as_slice()` may not provide enough information to Rust's type inference algorithm to determine the `ViewType`, leading to potentially confusing errors. Especially for teaching material and for making this more accessible to new Rust programmers, this proposal also includes convenience methods that are fixed to a specific type. +Some contexts of use of `as_slice()` may not provide enough information to Rust's type inference algorithm to determine the `BinaryViewType`, leading to potentially confusing errors. Especially for teaching material and for making this more accessible to new Rust programmers, this proposal also includes convenience methods that are fixed to a specific type. ```rust -impl ArrayBufferData { +impl BinaryData { fn as_u8_slice(&self) -> &[u8]; fn as_mut_u8_slice(&mut self) -> &mut [u8]; @@ -229,4 +229,4 @@ We could have added more API conveniences, including splitting views and working # Unresolved questions [unresolved]: #unresolved-questions -We need to determine the detailed requirements of the `ViewType` trait. This can be determined during the initial implementation work for this RFC. +None.