Might I ask what for? I don't see an immediate use-case for Symbol in a statically typed language. According to the MDN docs, Symbol is used
to add unique property keys to an object that won't collide with keys any other code might add to the object, and which are hidden from any mechanisms other code will typically use to access the object.
Rust does not have a mechanism for "other code" to add keys to an object. And if you want to restrict "access to the object", you can keep the fields you don't want accessible private.
That being said, if you want two instances of Symbol to never equal each other, you could implement PartialEq in a way that reflects that:
#[derive(Debug)]
struct Symbol;
impl PartialEq for Symbol {
fn eq(&self, _: &Self) -> bool { false }
}
const A: Symbol = Symbol;
const B: Symbol = Symbol;
fn main() {
assert_ne!(A, B);
}
In order to create guaranteed distinct items at compile time, you have to declare a new item of some kind; there is no way to have merely a const fn or other constant expression that returns a new distinct value when called. This means that creation of a symbol will need to be done through a macro. The two ways that come to mind to obtain distinct values are the TypeId of a new type or the address of a new static. Here is how to do it with a static:
use core::fmt;
#[derive(Clone, Copy)]
pub struct Symbol(pub &'static SymbolInner);
// The field is not used for anything but must be at least one byte.
// <https://doc.rust-lang.org/reference/items/static-items.html#r-items.static.storage-disjointness>
#[doc(hidden)]
pub struct SymbolInner(#[allow(unused)] u8);
macro_rules! symbol {
() => {
{
static UNIQUE: SymbolInner = SymbolInner(0);
Symbol(&UNIQUE)
}
}
}
impl PartialEq for Symbol {
fn eq(&self, other: &Self) -> bool {
// Compare the addresses of the `SymbolInner`s.
core::ptr::eq::<SymbolInner>(self.0, other.0)
}
}
impl Eq for Symbol {}
impl fmt::Debug for Symbol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Symbol({})", core::ptr::from_ref(self.0).addr())
}
}
#[test]
fn equality() {
const A: Symbol = symbol!();
const B: Symbol = symbol!();
let a = A;
assert_eq!(a, A);
assert_ne!(A, B);
}
This way also allows you to create symbols at run time but you have to Box::leak() to create a unique address for each one. A TypeId version would not allow creating symbols at run time at all, but would avoid needing a static allocation of one byte per symbol.
I don't see an immediate use-case for Symbol in a statically typed language.
I am using Rust to writing a draft of a conlang, where a word corresponds a constant, so every constant should have a different value. It's surely ok to use string directly to compare them (const HELLO: &str = "hello"), but since the constant name has already implied its meaning, writing it again would be just repeated work. So I was looking for a way to simplify it like Symbol.
That achieves the desired PartialEq behavior, but makes the symbols hard to actually do anything with since each one has a distinct type and they can't ever be passed around as a single type. I had in mind something more like the static version:
Now that I’ve written it down, it seems to me better in all ways than the static version. I had a vague notion that it would cost more compile time, but that’s probably unjustified.
This method doesn’t support creating multiple unique symbols in a loop (unsurprisingly, as that’s a runtime thing). I’d probably add something like a static std::sync::Once-based guard into the macro expansion so that this kind of misuse panics instead of producing an unintentional duplicate.