Skip to content

Commit

Permalink
Implement hashing for types
Browse files Browse the repository at this point in the history
  • Loading branch information
udoprog committed Aug 26, 2023
1 parent bb58c97 commit 98d5908
Show file tree
Hide file tree
Showing 12 changed files with 156 additions and 6 deletions.
3 changes: 2 additions & 1 deletion crates/rune/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ byte-code = ["alloc", "musli-storage"]
capture-io = ["alloc", "parking_lot"]
disable-io = ["alloc"]
fmt = ["alloc"]
std = ["num/std", "serde/std", "rune-core/std", "musli/std", "musli-storage/std", "alloc", "anyhow"]
std = ["num/std", "serde/std", "rune-core/std", "musli/std", "musli-storage/std", "alloc", "anyhow", "lazy_static"]
alloc = []

[dependencies]
Expand Down Expand Up @@ -74,6 +74,7 @@ similar = { version = "2.2.1", optional = true, features = ["inline", "bytes"] }
sha2 = { version = "0.10.6", optional = true }
base64 = { version = "0.21.0", optional = true }
rand = { version = "0.8.5", optional = true }
lazy_static = { version = "1.4.0", optional = true }

[dev-dependencies]
tokio = { version = "1.28.1", features = ["full"] }
Expand Down
1 change: 1 addition & 0 deletions crates/rune/src/compile/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ impl Context {
this.install(crate::modules::any::module()?)?;
this.install(crate::modules::bytes::module()?)?;
this.install(crate::modules::char::module()?)?;
this.install(crate::modules::hash::module()?)?;
this.install(crate::modules::cmp::module()?)?;
this.install(crate::modules::collections::module()?)?;
this.install(crate::modules::f64::module()?)?;
Expand Down
4 changes: 2 additions & 2 deletions crates/rune/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ pub(crate) use rune_macros::__internal_impl_any;
/// }
///
/// /// Construct a new [`Struct`].
/// #[rune::function(path = Struct::new)]
/// #[rune::function(free, path = Struct::new)]
/// fn new() -> Struct {
/// Struct {
/// /* .. */
Expand Down Expand Up @@ -511,7 +511,7 @@ pub(crate) use rune_macros::__internal_impl_any;
/// /// let string = String::empty();
/// /// assert_eq!(string, "hello");
/// /// ```
/// #[rune::function(path = String::empty)]
/// #[rune::function(free, path = String::empty)]
/// fn empty() -> String {
/// String {
/// inner: std::string::String::new()
Expand Down
2 changes: 1 addition & 1 deletion crates/rune/src/module/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -955,7 +955,7 @@ impl Module {
/// }
///
/// /// Construct a new [`Struct`].
/// #[rune::function(path = Struct::new)]
/// #[rune::function(free, path = Struct::new)]
/// fn new() -> Struct {
/// Struct {
/// /* .. */
Expand Down
1 change: 1 addition & 0 deletions crates/rune/src/modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub mod f64;
pub mod fmt;
pub mod future;
pub mod generator;
pub mod hash;
pub mod i64;
#[cfg(feature = "std")]
pub mod io;
Expand Down
16 changes: 16 additions & 0 deletions crates/rune/src/modules/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//! The `std::hash` module.
use crate as rune;
#[cfg(feature = "std")]
use crate::runtime::Hasher;
use crate::{ContextError, Module};

#[rune::module(::std::hash)]
/// Types for dealing with hashing in Rune.
pub fn module() -> Result<Module, ContextError> {
#[allow(unused_mut)]
let mut module = Module::from_meta(self::module_meta);
#[cfg(feature = "std")]
module.ty::<Hasher>()?;
Ok(module)
}
47 changes: 47 additions & 0 deletions crates/rune/src/modules/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
use core::cmp::Ordering;

use crate as rune;
#[cfg(feature = "std")]
use crate::runtime::Hasher;
use crate::runtime::{
ControlFlow, EnvProtocolCaller, Function, Generator, GeneratorState, Iterator, Range,
RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive, Value, Vm, VmResult,
};
use crate::{ContextError, Module};
#[cfg(feature = "std")]
use std::collections::hash_map::RandomState;

#[rune::module(::std::ops)]
/// Overloadable operators.
Expand Down Expand Up @@ -96,6 +100,8 @@ pub fn module() -> Result<Module, ContextError> {
m.function_meta(eq)?;
m.function_meta(partial_cmp)?;
m.function_meta(cmp)?;
#[cfg(feature = "std")]
m.function_meta(hash)?;
Ok(m)
}

Expand Down Expand Up @@ -201,6 +207,47 @@ fn cmp(lhs: Value, rhs: Value) -> VmResult<Ordering> {
Value::cmp(&lhs, &rhs)
}

#[cfg(feature = "std")]
lazy_static::lazy_static! {
static ref STATE: RandomState = RandomState::new();
}

/// Hashes the given value.
///
/// For non-builtin types this uses the [`HASH`] protocol.
///
/// # Hash stability
///
/// The hash is guaranteed to be stable within a single virtual machine
/// invocation, but not across virtual machines. So returning the hash from one
/// and calculating it in another using an identical value is not guaranteed to
/// produce the same hash.
///
/// # Panics
///
/// Panics if we try to generate a hash from an unhashable value.
///
/// # Examples
///
/// ```rune
/// use std::ops::hash;
///
/// assert_eq!(hash([1, 2]), hash((1, 2)));
/// ```
#[rune::function]
#[cfg(feature = "std")]
fn hash(value: Value) -> VmResult<i64> {
let mut hasher = Hasher::new_with(&*STATE);

vm_try!(Value::hash_with(
&value,
&mut hasher,
&mut EnvProtocolCaller
));

VmResult::Ok(hasher.finish() as i64)
}

/// Advance a generator producing the next value yielded.
///
/// Unlike [`Generator::resume`], this can only consume the yielded values.
Expand Down
21 changes: 21 additions & 0 deletions crates/rune/src/modules/tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
use core::cmp::Ordering;

use crate as rune;
#[cfg(feature = "std")]
use crate::runtime::Hasher;
use crate::runtime::{EnvProtocolCaller, Iterator, Ref, Tuple, Value, Vec, VmResult};
use crate::{ContextError, Module};

Expand All @@ -20,6 +22,8 @@ pub fn module() -> Result<Module, ContextError> {
m.function_meta(eq)?;
m.function_meta(partial_cmp)?;
m.function_meta(cmp)?;
#[cfg(feature = "std")]
m.function_meta(hash)?;
Ok(m)
}

Expand Down Expand Up @@ -174,3 +178,20 @@ fn partial_cmp(this: &Tuple, other: &Tuple) -> VmResult<Option<Ordering>> {
fn cmp(this: &Tuple, other: &Tuple) -> VmResult<Ordering> {
Vec::cmp_with(this, other, &mut EnvProtocolCaller)
}

/// Calculate a hash for a tuple.
///
/// # Examples
///
/// ```rune
/// use std::ops::hash;
///
/// assert_eq!(hash((0, 2, 3)), hash((0, 2, 3)));
/// // Note: this is not guaranteed to be true forever, but it's true right now.
/// assert_eq!(hash((0, 2, 3)), hash([0, 2, 3]));
/// ```
#[rune::function(instance, protocol = HASH)]
#[cfg(feature = "std")]
fn hash(this: &Tuple, hasher: &mut Hasher) -> VmResult<()> {
Tuple::hash_with(this, hasher, &mut EnvProtocolCaller)
}
19 changes: 19 additions & 0 deletions crates/rune/src/modules/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use core::cmp::Ordering;
use core::fmt;

use crate as rune;
#[cfg(feature = "std")]
use crate::runtime::Hasher;
use crate::runtime::{
EnvProtocolCaller, Formatter, Function, Iterator, Ref, TypeOf, Value, Vec, VmErrorKind,
VmResult,
Expand Down Expand Up @@ -54,6 +56,8 @@ pub fn module() -> Result<Module, ContextError> {
m.function_meta(eq)?;
m.function_meta(partial_cmp)?;
m.function_meta(cmp)?;
#[cfg(feature = "std")]
m.function_meta(hash)?;
Ok(m)
}

Expand Down Expand Up @@ -615,3 +619,18 @@ fn partial_cmp(this: &Vec, other: &Vec) -> VmResult<Option<Ordering>> {
fn cmp(this: &Vec, other: &Vec) -> VmResult<Ordering> {
Vec::cmp_with(this, other, &mut EnvProtocolCaller)
}

/// Calculate the hash of a vector.
///
/// # Examples
///
/// ```rune
/// use std::ops::hash;
///
/// assert_eq!(hash([0, 2, 3]), hash([0, 2, 3]));
/// ```
#[rune::function(instance, protocol = HASH)]
#[cfg(feature = "std")]
fn hash(this: &Vec, hasher: &mut Hasher) -> VmResult<()> {
Vec::hash_with(this, hasher, &mut EnvProtocolCaller)
}
15 changes: 15 additions & 0 deletions crates/rune/src/runtime/tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use crate::runtime::{
ConstValue, FromValue, Mut, RawMut, RawRef, Ref, Shared, ToValue, UnsafeToMut, UnsafeToRef,
Value, VmErrorKind, VmResult,
};
#[cfg(feature = "std")]
use crate::runtime::{Hasher, ProtocolCaller};
use crate::Any;

/// The type of a tuple slice.
Expand Down Expand Up @@ -44,6 +46,19 @@ impl Tuple {

VmResult::Ok(Some(vm_try!(T::from_value(value))))
}

#[cfg(feature = "std")]
pub(crate) fn hash_with(
&self,
hasher: &mut Hasher,
caller: &mut impl ProtocolCaller,
) -> VmResult<()> {
for value in self.values.iter() {
vm_try!(value.hash_with(hasher, caller));
}

VmResult::Ok(())
}
}

impl ops::Deref for Tuple {
Expand Down
18 changes: 16 additions & 2 deletions crates/rune/src/runtime/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ use crate::no_std::vec;

use crate::compile::ItemBuf;
use crate::runtime::vm::CallResult;
#[cfg(feature = "std")]
use crate::runtime::Hasher;
use crate::runtime::{
AccessKind, AnyObj, Bytes, ConstValue, ControlFlow, EnvProtocolCaller, Format, Formatter,
FromValue, FullTypeOf, Function, Future, Generator, GeneratorState, Iterator, MaybeTypeOf, Mut,
Object, OwnedTuple, Protocol, ProtocolCaller, Range, RangeFrom, RangeFull, RangeInclusive,
RangeTo, RangeToInclusive, RawMut, RawRef, Ref, Shared, Stream, ToValue, Type, TypeInfo,
Variant, Vec, Vm, VmError, VmErrorKind, VmIntegerRepr, VmResult,
};
#[cfg(feature = "std")]
use crate::runtime::{Hasher, Tuple};
use crate::{Any, Hash};

use serde::{de, ser, Deserialize, Serialize};
Expand Down Expand Up @@ -1339,6 +1339,12 @@ impl Value {
})
}

/// Hash the current value.
#[cfg(feature = "std")]
pub fn hash(&self, hasher: &mut Hasher) -> VmResult<()> {
self.hash_with(hasher, &mut EnvProtocolCaller)
}

/// Hash the current value.
#[cfg(feature = "std")]
pub(crate) fn hash_with(
Expand Down Expand Up @@ -1377,6 +1383,14 @@ impl Value {
hasher.write(&bytes);
return VmResult::Ok(());
}
Value::Tuple(tuple) => {
let tuple = vm_try!(tuple.borrow_ref());
return Tuple::hash_with(&tuple, hasher, caller);
}
Value::Vec(vec) => {
let vec = vm_try!(vec.borrow_ref());
return Vec::hash_with(&vec, hasher, caller);
}
value => {
match vm_try!(caller.try_call_protocol_fn(Protocol::HASH, value.clone(), (hasher,)))
{
Expand Down
15 changes: 15 additions & 0 deletions crates/rune/src/runtime/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use crate::no_std::prelude::*;
use crate::no_std::vec;

use crate as rune;
#[cfg(feature = "std")]
use crate::runtime::Hasher;
use crate::runtime::{
Formatter, FromValue, Iterator, ProtocolCaller, RawRef, Ref, Shared, ToValue, UnsafeToRef,
Value, VmErrorKind, VmResult,
Expand Down Expand Up @@ -379,6 +381,19 @@ impl Vec {

VmResult::Ok(Some(Value::vec(values.to_vec())))
}

#[cfg(feature = "std")]
pub(crate) fn hash_with(
&self,
hasher: &mut Hasher,
caller: &mut impl ProtocolCaller,
) -> VmResult<()> {
for value in self.inner.iter() {
vm_try!(value.hash_with(hasher, caller));
}

VmResult::Ok(())
}
}

impl fmt::Debug for Vec {
Expand Down

0 comments on commit 98d5908

Please sign in to comment.