Skip to content

Commit

Permalink
rune: Implement support for traits
Browse files Browse the repository at this point in the history
  • Loading branch information
udoprog committed Jul 26, 2024
1 parent f6ec4c9 commit 816e003
Show file tree
Hide file tree
Showing 117 changed files with 7,963 additions and 5,103 deletions.
2 changes: 1 addition & 1 deletion benches/benches/benchmarks/aoc_2020_19b.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fn aoc_2020_19b(b: &mut Criterion) {
let mut vm = rune_vm! {
use std::collections::HashMap;
fn get_rules() {
HashMap::from([
HashMap::from_iter([
(118, Rule::Or(Rule::Seq([29, 95]), Rule::Seq([106, 58]))),
(64, Rule::Or(Rule::Seq([29, 63]), Rule::Seq([106, 89]))),
(112, Rule::Or(Rule::Seq([106, 98]), Rule::Seq([29, 60]))),
Expand Down
1 change: 1 addition & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [Template literals](./template_literals.md)
- [Instance functions](./instance_functions.md)
- [Field functions](./field_functions.md)
- [Traits](./traits.md)
- [Built-in types](./types.md)
- [Primitives and references](./primitives.md)
- [Vectors](./vectors.md)
Expand Down
144 changes: 144 additions & 0 deletions book/src/traits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Traits

Traits in rune defines a collection associated items. Once a trait is
implemented by a type we can be sure that all the associated names it defines
are present on the type.

Traits allow us to reason about types more abstractly, such as this is an
iterator.

#### Limits

As usual, Rune doesn't permit more than one definition of an associated name.
Attempting to define more than one with the same name results in a build-time
error. This is in contrast to Rust which allows multiple traits with overlapping
methods to be defined. So why doesn't Rune allow for this?

Since Rune is a dynamic language, consider what would happen in a situation like
this:

```rust
struct Foo {
/* .. */
}

impl Iterator for Foo {
fn next(self) {
/* .. */
}
}

impl OtherIterator for Foo {
fn next(self) {
/* .. */
}
}

let foo = Foo {
/* .. */
};

// Which implementation of `next` should we call?
while let Some(value) = foo.next() {

}
```

Since there are no type parameters we can't solve the ambiguity by either only
having one trait defining the method in scope or by using an unambigious
function qualified function call.

#### Implementation

In the background the user-facing implementation of traits is done by
implementing protocols just before. Protocols are still used by the virtual
machine to call functions.

The separation that protocols provide is important because we don't want a user
to *accidentally* implement an associated method which would then be picked up
by a trait. Protocols are uniquely defined in their own namespace and cannot be
invoked in user code.

As an example, to implement the `Iterator` trait you have to implement the
`NEXT` protocol. So if the `NEXT` protocol is present and we request that the
`::std::iter::Iterator` trait should be implemented, the `NEXT` protocol
implementation is used to construct all the relevant associated methods. This is
done by calling `Module::implement_trait`.

```rust
let mut m = Module::with_item(["module"]);
m.ty::<Iter>()?;
m.function_meta(Iter::next__meta)?;
m.function_meta(Iter::size_hint__meta)?;
m.implement_trait::<Iter>(rune::item!(::std::iter::Iterator))?;

#[derive(Any)]
#[rune(item = "module")]
struct Iter {
/* .. */
}

impl Iter {
#[rune::function(keep, protocol = NEXT)]
fn size_hint(&self) -> Option<bool> {
Some(true)
}

#[rune::function(keep, protocol = SIZE_HINT)]
fn size_hint(&self) -> (usize, Option<usize>) {
(1, None)
}
}
```

Note that this allows the `Iter` type above to specialize its `SIZE_HINT`
implementation. If the `SIZE_HINT` protocol was not defined, a default
implementation would be provided by the trait.

As a result of implementing the `::std::iter::Iterator` trait, the `Iter` type
now *automatically* gets all the iterator-associated function added to it. So
not only can you call `Iter::next` to advance the iterator, but also make use of
combinators such as `filter`:

```rust
let it = /* construct Iter */;

for value in it.filter(|v| v != true) {
dbg!(value);
}
```

#### Defining a trait

Defining a trait is currently a low-level module operation. It's done by
implementing a handler which will be called to populate the relevant methods
when the trait is implement. Such as this snippet for the `Iterator` trait:

```rust
let mut m = Module::with_crate("std", ["iter"]);

let mut t = m.define_trait(["Iterator"])?;

t.handler(|cx| {
let next = cx.find(Protocol::NEXT)?;
cx.function_handler("next", &next)?;

let size_hint = if let Some(size_hint) = cx.try_find(Protocol::SIZE_HINT)? {
cx.function_handler("size_hint", &size_hint)?;
size_hint
} else {
let size_hint = cx.function("size_hint", |_: Value| (0usize, None::<usize>))?;
cx.function_handler(Protocol::SIZE_HINT, &size_hint)?;
size_hint
};

/* more methods */
Ok(())
})?;
```

Calling `find` requires that `NEXT` is implemented. We can also see that the
implementation for `SIZE_HINT` will fall back to a default implementation if
it's not implemented. The appropriate protocol is also populated if it's
missing. All the relevant associated functions are also provided, such as
`value.next()` and `value.size_hint()`.
4 changes: 2 additions & 2 deletions crates/rune-alloc/src/btree/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ type BoxedNode<K, V> = NonNull<LeafNode<K, V>>;
///
/// This type has a number of parameters that controls how it acts:
/// - `BorrowType`: A dummy type that describes the kind of borrow and carries a lifetime.
/// Since any `NodeRef` allows navigating through the tree, `BorrowType`
/// effectively applies to the entire tree, not just to the node itself.
/// - When this is `Immut<'a>`, the `NodeRef` acts roughly like `&'a Node`.
/// - When this is `ValMut<'a>`, the `NodeRef` acts roughly like `&'a Node`
/// with respect to keys and tree structure, but also allows many
Expand All @@ -152,8 +154,6 @@ type BoxedNode<K, V> = NonNull<LeafNode<K, V>>;
/// - When this is `Dying`, the `NodeRef` still acts roughly like `Box<Node>`,
/// but has methods to destroy the tree bit by bit, and ordinary methods,
/// while not marked as unsafe to call, can invoke UB if called incorrectly.
/// Since any `NodeRef` allows navigating through the tree, `BorrowType`
/// effectively applies to the entire tree, not just to the node itself.
/// - `K` and `V`: These are the types of keys and values stored in the nodes.
/// - `Type`: This can be `Leaf`, `Internal`, or `LeafOrInternal`. When this is
/// `Leaf`, the `NodeRef` points to a leaf node, when this is `Internal` the
Expand Down
3 changes: 2 additions & 1 deletion crates/rune-alloc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
//! <br>
//!
//! The Rune Language, an embeddable dynamic programming language for Rust.
// Quite a few parts copied from the Rust Project under the MIT license.
//
// Copyright 2014-2023 The Rust Project Developers
// Copyright 2014-2024 The Rust Project Developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT
Expand Down
20 changes: 6 additions & 14 deletions crates/rune-core/src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,17 @@ const IDENT: u64 = 0x1a095090689d4647;
const INDEX: u64 = 0xe1b2378d7a937035;

// Salt for type parameters.
const TYPE_PARAMETERS: u64 = 0x9d30e58b77e4599;
const TYPE_PARAMETERS: u32 = 16;
// Salt for function parameters.
const FUNCTION_PARAMETERS: u64 = 0x6052c152243a6eb3;
const FUNCTION_PARAMETERS: u32 = 48;

/// The primitive hash that among other things is used to reference items,
/// types, and native functions.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "musli", derive(Decode, Encode))]
#[repr(transparent)]
#[cfg_attr(feature = "musli", musli(transparent))]
pub struct Hash(u64);
pub struct Hash(#[doc(hidden)] pub u64);

impl Hash {
/// The empty hash.
Expand Down Expand Up @@ -158,20 +158,12 @@ impl Hash {

/// Mix the current hash with type parameters.
pub const fn with_type_parameters(self, ty: Self) -> Self {
if !ty.is_empty() {
Self(self.0 ^ (ty.0 ^ TYPE_PARAMETERS))
} else {
self
}
Self(self.0 ^ ty.0.wrapping_shl(TYPE_PARAMETERS))
}

/// Mix the current hash with function parameters.
pub const fn with_function_parameters(self, f: Self) -> Self {
if !f.is_empty() {
Self(self.0 ^ (f.0 ^ FUNCTION_PARAMETERS))
} else {
self
}
Self(self.0 ^ f.0.wrapping_shl(FUNCTION_PARAMETERS))
}

/// Hash type parameters.
Expand Down
6 changes: 1 addition & 5 deletions crates/rune-core/src/item/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,7 @@ impl Item {
/// assert_eq!(item2.last(), Some(ComponentRef::Str("world")));
/// # Ok::<(), rune::support::Error>(())
/// ```
pub fn join<I>(&self, other: I) -> alloc::Result<ItemBuf>
where
I: IntoIterator,
I::Item: IntoComponent,
{
pub fn join(&self, other: impl IntoIterator<Item: IntoComponent>) -> alloc::Result<ItemBuf> {
let mut content = self.content.try_to_owned()?;

for c in other {
Expand Down
15 changes: 5 additions & 10 deletions crates/rune-core/src/item/item_buf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,10 @@ impl<A: Allocator> ItemBuf<A> {
}

/// Construct a new item with the given path in the given allocator.
pub(crate) fn with_item_in<I>(iter: I, alloc: A) -> alloc::Result<Self>
where
I: IntoIterator,
I::Item: IntoComponent,
{
pub(crate) fn with_item_in(
iter: impl IntoIterator<Item: IntoComponent>,
alloc: A,
) -> alloc::Result<Self> {
let mut content = Vec::new_in(alloc);

for c in iter {
Expand Down Expand Up @@ -184,11 +183,7 @@ impl ItemBuf {
/// assert_eq!(it.next(), None);
/// # Ok::<(), rune::support::Error>(())
/// ```
pub fn with_item<I>(iter: I) -> alloc::Result<Self>
where
I: IntoIterator,
I::Item: IntoComponent,
{
pub fn with_item(iter: impl IntoIterator<Item: IntoComponent>) -> alloc::Result<Self> {
Self::with_item_in(iter, Global)
}

Expand Down
60 changes: 42 additions & 18 deletions crates/rune-core/src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,46 @@ define! {
doc: ["Allows iteration to be advanced for the type, this is used for iterators."],
};

/// The function to call to continue iteration at the nth element.
pub const [NTH, NTH_HASH]: Protocol = Protocol {
name: "nth",
hash: 0x6704550736c82a58u64,
repr: None,
doc: ["Allows iteration to be advanced for the type to the nth element, this is used for iterators."],
};

/// The function to call to continue iteration at the nth element form the back.
pub const [NTH_BACK, NTH_BACK_HASH]: Protocol = Protocol {
name: "nth_back",
hash: 0x4885ca2fd53a08c8u64,
repr: None,
doc: ["Allows iteration to be advanced for the type to the nth element from the back, this is used for iterators."],
};

/// Protocol used when getting the size hint of an iterator.
pub const [SIZE_HINT, SIZE_HINT_HASH]: Protocol = Protocol {
name: "size_hint",
hash: 0x1a7b50baabc6e094u64,
repr: Some("let output = $value.size_hint()"),
doc: ["Get the size hint of an iterator."],
};

/// Protocol used when getting the exact length of an iterator.
pub const [LEN, LEN_HASH]: Protocol = Protocol {
name: "len",
hash: 0x52dd3b9489d39c42u64,
repr: Some("let output = $value.len()"),
doc: ["Get the length of an iterator."],
};

/// Protocol used when cloning a value.
pub const [NEXT_BACK, NEXT_BACK_HASH]: Protocol = Protocol {
name: "next_back",
hash: 0x91149fef42c0a8aeu64,
repr: Some("let output = $value.next_back()"),
doc: ["Get the next value from the back of the iterator."],
};

/// Function used to convert an argument into a future.
///
/// Signature: `fn(Value) -> Future`.
Expand Down Expand Up @@ -492,30 +532,14 @@ define! {
name: "hash",
hash: 0xf6cf2d9f416cef08u64,
repr: Some("let output = hash($value)"),
doc: ["Hash the given value."],
doc: ["Hash a value."],
};

/// Protocol used when cloning a value.
pub const [CLONE, CLONE_HASH]: Protocol = Protocol {
name: "clone",
hash: 0x2af2c875e36971eu64,
repr: Some("let output = clone($value)"),
doc: ["Clone the given value."],
};

/// Protocol used when cloning a value.
pub const [SIZE_HINT, SIZE_HINT_HASH]: Protocol = Protocol {
name: "size_hint",
hash: 0x3de0975a7000dau64,
repr: Some("let output = $value.size_hint()"),
doc: ["Get the size hint of the given iterator."],
};

/// Protocol used when cloning a value.
pub const [NEXT_BACK, NEXT_BACK_HASH]: Protocol = Protocol {
name: "next_back",
hash: 0x91149fef42c0a8aeu64,
repr: Some("let output = $value.next_back()"),
doc: ["Get the next value from the back of the iterator."],
doc: ["Clone a value."],
};
}
Loading

0 comments on commit 816e003

Please sign in to comment.