Because I don't have all the context that you have.
Often if I can see the exact code you are trying to run and the errors you are running into, I can understand what you are trying to achieve and how the code can be altered to get there.
For example, the code in your playground link uses a string literal for the key (i.e. strategy
is inferred to be HashMap<&'static str, Strategy>
) and is running into a "error[E0277]: the trait bound &str: Borrow<String>
is not satisfied" error, while the code in your original post uses a String
for the key and runs into a "no method named callback
found for enum Option
in the current scope" error.
This is why I ask for a link to the code on the playground - so I can reproduce it and we are both talking about the same code.
Assuming we are starting with the playground link, this is the error we run into:
error[E0277]: the trait bound `&str: Borrow<String>` is not satisfied
--> src/main.rs:25:26
|
25 | let x = strategy.get(&String::from("strategy_id"));
| --- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Borrow<String>` is not implemented for `&str`
| |
| required by a bound introduced by this call
|
= help: the trait `Borrow<str>` is implemented for `String`
note: required by a bound in `HashMap::<K, V, S>::get`
That's because we did strategy.insert("strategy_id", ...)
higher up and the Rust compiler inferred that strategy
's key type is a &'static str
(i.e. a reference to a string literal). When we call get()
, it expects the value to be a &Q
where K: Borrow<Q>
(i.e. we can call .borrow()
on our key and get something of type Q
).
However &str
(a reference to some text somewhere) doesn't have a Borrow
impl that gives us a reference to a String
(i.e. a heap-allocated object). We do have a Borrow
implementation that goes in the other direction, though.
TL;DR:
let mut strategy: HashMap<String, Strategy> = HashMap::new();
strategy.insert(String::from("strategy_id"), Strategy { ... });
let x = strategy.get("strategy_id");
(playground)
(we could have also used a &'static str
string literal for all our keys, but that gets awkward later on when you start needing to use keys that are only known at runtime)
That change fixed our first error, but the code still doesn't compile!
error[E0599]: no method named `callback` found for enum `Option` in the current scope
--> src/main.rs:21:7
|
21 | x.callback()
| ^^^^^^^^ method not found in `Option<&Strategy>`
This is because strategy.get()
doesn't return a &Strategy
, it returns an Option<&Strategy>
. The Option
type is a wrapper that is roughly equivalent to T | undefined
in TypeScript and indicates that sometimes the hashmap won't contain the value you want. You need to handle this in some way, but the easiest (for now) is to call Option
's unwrap()
method and crash the program if get()
failed.
let x = strategy.get("strategy_id").unwrap();
(playground)
We now have a &Strategy
stored in x
, but the code still doesn't compile.
error[E0599]: no method named `callback` found for reference `&Strategy` in the current scope
--> src/main.rs:21:7
|
21 | x.callback()
| ^^^^^^^^ field, not a method
|
help: to call the function stored in `callback`, surround the field access with parentheses
|
21 | (x.callback)()
| + +
Luckily, the compiler also gave us a suggestion that fixes the issue. If you read the full error message, you can also see exactly why it's complaining... The x.callback()
syntax is for calling a method called callback()
on the Strategy
type. However, calling a function that's stored in a field requires you to explicitly say so with (x.callback)()
- Rust's syntax treats field access and method calls separately!
Here is the full working code:
#![allow(unused)]
use std::collections::HashMap;
struct Strategy {
pub callback: fn(),
}
fn main() {
let mut strategy: HashMap<String, Strategy> = HashMap::new();
strategy.insert(
String::from("strategy_id"),
Strategy {
callback: || println!("{}", "Hi Callback!"),
},
);
let x = strategy.get("strategy_id").unwrap();
(x.callback)();
}
(playground)
And hitting "run" shows the expected output:
--- Standard Error ---
Compiling playground v0.0.1 (/playground)
Finished dev [unoptimized + debuginfo] target(s) in 3.70s
Running `target/debug/playground`
--- Standard Output ---
Hi Callback!
Differentiating between calling a method and accessing a field which may be a function may seem a bit silly if you come from the JavaScript world, but there are actually some really good reasons to have different syntax.
First and foremost, the two forms do very different things. Calling a method is actually syntactic sugar for invoking a free function called (for example) Strategy::callback
and passing in a &Strategy
reference as the first argument. On the other hand, invoking a function pointer stored in a field involves dereferencing the &Strategy
and reading the pointer out of the callback
field, then invoking it without passing in that first &self
parameter. The first form is really easy to optimise, whereas the second form involves jumping into some machine code only known at runtime.
The second reason is that it lets you create a private field called name
with a public getter called name()
. This way, we can prevent people from modifying our field from the outside (like you would if the field was pub
), but at the same time we don't need to prefix every single getter with get_
. This is a big win for both privacy and ergonomics.
struct Person {
name: String,
}
impl Person {
pub fn name(&self) -> &str { &self.name }
}
(playground)