Rust convert a Vec<&str> to a single &str - rust

edit: I'm really dumb, I forgot that vectors need constant sized elements :)
I want to convert a Vec<&str> to a single &str.
As an example I want {"a","bc"} -> "abc"
I know you can use the join("") method to accomplish this, but because the memory is already laid out correctly this feels redundant. I wanted to do this a different way.
I wrote a function to do this, but it uses unsafe. Is there a better/cleaner way to do this?
fn vec_to_str(input: Vec<&str>) -> Option<&str> {
let last_elem = input.last()?;
unsafe {
let last = last_elem.as_bytes().as_ptr().add(last_elem.len());
let first = input.first()?.as_bytes().as_ptr();
let len = last.offset_from(first);
Some(str::from_utf8_unchecked(from_raw_parts(first, len.try_into().unwrap())))
}
}

You cannot get a combined &str from an arbitrary Vec<&str> without creating a separate allocation. You are correct that Vec stores its elements contiguously, but those elements are &str which are references. The references are stored contiguously, but that means nothing in regard to how the referenced data is organized. The memory structure of input would look akin to this:
+----------+ +----------+ +----------+
| ptr |----->| [0].ptr |----->| "a" |
+----------+ +----------+ +----------+
| size | | [0].size |
+----------+ +----------+ +----------+
| capacity | | [1].ptr |----->| "bc" |
+----------+ +----------+ +----------+
| [1].size |
+----------+
So you should simply create a new String using .join("").

Related

Rust / Yew Geolocation

I'm trying to retrieve the user's lat/lon, once, in the create function of a Yew Component, so that I can do some math and pass useful decisions to child components. I've made the Yew hook "use_geolocation()" work just fine, but that only runs in a function_component, and there doesn't appear to be any straightforward way to use that location in other components.
Then I found this neat tutorial which uses wasm_bindgen and Seed::app::orders::Orders to make a "cheap clone" of the app, and to call the Javascript functions. The bindings look like:
#[wasm_bindgen]
extern "C" {
type GeolocationCoordinates;
#[wasm_bindgen(method, getter)]
fn latitude(this: &GeolocationCoordinates) -> f64;
#[wasm_bindgen(method, getter)]
fn longitude(this: &GeolocationCoordinates) -> f64;
type GeolocationPosition;
#[wasm_bindgen(method, getter)]
fn coords(this: &GeolocationPosition) ->
GeolocationCoordinates;
}
And the function for fetching geolocation, splicing together the chunks of code from Tor Hovland's tutorial:
let (app, msg_mapper) = (orders.clone_app(), orders.msg_mapper());
let geo_callback = move |position: JsValue| {
let pos: GeolocationPosition = position.into();
let coords = pos.coords();
app.update(msg_mapper(Msg::Position(
coords.latitude(),
coords.longitude(),
)));
};
let geolocation = web_sys::window()
.expect("Unable to get browser window.")
.navigator()
.geolocation()
.expect("Unable to get geolocation.");
let geo_callback_function =
Closure::wrap(
Box::new(|pos| geo_callback(pos)) as Box<dyn Fn(JsValue)>
);
geolocation.get_current_position(
&geo_callback_function.as_ref().unchecked_ref()
).expect("Unable to get position");
geo_callback_function.forget();
I attempted this route, but found that adding the line seed = "0.9.1" into my Cargo.toml produced compile errors, something about a type mismatch between a closure in wasm_bindgen and something in seed. Included here for completeness:
error[E0283]: type annotations needed for `Closure<T>`
--> /home/djmcmath/.cargo/registry/src/github.com-1ecc6299db9ec823/seed-
0.9.1/src/browser/service/routing.rs:87:9
|
87 | let closure = Closure::new(move |event: web_sys::Event| {
| ^^^^^^^
|
= note: cannot satisfy `_: WasmClosure`
note: required by a bound in `Closure::<T>::new`
--> /home/djmcmath/.cargo/registry/src/github.com-1ecc6299db9ec823/wasm-bindgen-
0.2.81/src/closure.rs:251:17
|
251 | T: ?Sized + WasmClosure,
| ^^^^^^^^^^^ required by this bound in `Closure::<T>::new`
help: consider giving `closure` an explicit type, where the type for type parameter
`T` is specified
|
87 | let closure: Closure<T> = Closure::new(move |event: web_sys::Event| {
| ++++++++++++
help: consider specifying the type argument in the function call
|
87 | let closure = Closure::new::<F>(move |event: web_sys::Event| {
| +++++
After beating my head against that brick wall for a while, I decided to just not use Seed, but I don't know of another way to make a "cheap clone" of the app to make the lifetimes work out correctly. Without that, I get the predictable lifetime error on the geo_callback_function:
let geo_callback_function =
Closure::wrap(
Box::new(|pos: JsValue| geo_callback(pos)) as Box<dyn Fn(JsValue)>
);
Error message:
error[E0597]: `geo_callback` does not live long enough
--> src/ferry_route.rs:213:37
|
213 | Box::new(|pos: JsValue| geo_callback(pos)) as Box<dyn Fn(JsValue)>
| ------------------------^^^^^^^^^^^^------
| | | |
| | | borrowed value does not live long enough
| | value captured here
| cast requires that `geo_callback` is borrowed for `'static`
...
221 | }
| - `geo_callback` dropped here while still borrowed
So I'm at a loss, at this point. Seems like fetching user's location would be simpler than all of this. I'm open to any path that makes any of these work. (Definition of work: I can get the user's lat/lon in the create function of a Yew Component, so that I can do some math and pass useful decisions to child components.)
Ok, I think I've got it. Or at least, I've got something that works. I'm not sure it's the most elegant solution. Basically, drop the Seed parts out of the function and just pull the coords from the JsValue, like so:
fn geo_callback(position: JsValue) {
let pos = JsCast::unchecked_into::<GeolocationPosition>(position);
let coords = pos.coords();
log::info!(
"Latitude: {}. Longitude: {}.",
coords.latitude(),
coords.longitude()
);
};
At this point, I'm pretty sure I can take those coords and do something useful with them.

Default value from Option<String>

How can I return a default value from an Option<&String>?
This is my sample/minimal code:
fn main() {
let map = std::collections::HashMap::<String, String>::new();
let result = map.get("").or_else(|| Some("")).unwrap(); // <== I tried lots of combinations
println!("{}", result);
}
I know I could do something like this...
let value = match result {
Some(v) => v,
None => "",
};
... but I want to know if it is possible to implement it in a one-liner with or_else or unwrap_or_else?
(It is important to make the default value lazy, so it does not get computed if it is not used)
These are some of the compiler suggestions I tried (I can put them all because SO won't allow me):
7 | let result = map.get("").or_else(|| Some("") ).unwrap();
| ^^ expected struct `String`, found `str`
.
7 | let result = map.get("").or_else(|| Some(&"".to_string()) ).unwrap();
| ^^^^^^--------------^
| | |
| | temporary value created here
| returns a value referencing data owned by the current function
.
7 | let result = map.get("").or_else(|| Some(String::new()) ).unwrap();
| ^^^^^^^^^^^^^
| |
| expected `&String`, found struct `String`
|
help: consider borrowing here: `&String::new()`
.
7 | let result = map.get("").or_else(|| Some(&String::new()) ).unwrap();
| ^^^^^^-------------^
| | |
| | temporary value created here
| returns a value referencing data owned by the current function
.
and also
6 | let result = map.get("").unwrap_or_else(|| ""); // I tried lots
| ^^ expected struct `String`, found `str`
|
= note: expected reference `&String`
found reference `&'static str`
If you really need a &String as the result, you may create a String for the default value with lifetime that's long enough.
fn main() {
let map = std::collections::HashMap::<String, String>::new();
let default_value = "default_value".to_string();
let result = map.get("").unwrap_or(&default_value);
println!("{}", result);
}
If the default value is a compile-time fixed value, the allocation of default_value can be avoided by using &str instead.
fn main() {
let map = std::collections::HashMap::<String, String>::new();
let result = map.get("")
.map(String::as_str)
.unwrap_or("default_value");
println!("{}", result);
}
How can I return a default value from an Option<&String>?
It's not trivial because as you've discovered ownership gets in the way, as you need an actual String to create an &String. The cheap and easy solution to that is to just have a static empty String really:
static DEFAULT: String = String::new();
fn main() {
let map = std::collections::HashMap::<String, String>::new();
let result = map.get("").unwrap_or(&DEFAULT); // <== I tried lots of combinations
println!("{}", result);
}
String::new is const since 1.39.0, and does not allocate, so this works fine. If you want a non-empty string as default value it's not as good a solution though.
The cleaner and more regular alternative is to "downgrade" (or upgrade, depending on the POV) the &String to an &str:
let result = map.get("").map(String::as_str).unwrap_or("");
or
let result = map.get("").map(|s| &**s).unwrap_or("");
it's really not like you're losing anything here, as &String is not much more capable than &str (it does offer a few more thing e.g. String::capacity, but for the most part it exists on genericity grounds e.g. HashMap::<K, V>::get returns an &V, so if you store a String you get an &String makes sense even though it's not always quite the thing you want most).

Fold with string array

I tried some code like this:
fn main() {
let a = vec!["May", "June"];
let s = a.iter().fold("", |s2, s3|
s2 + s3
);
println!("{}", s == "MayJune");
}
Result:
error[E0369]: cannot add `&&str` to `&str`
--> a.rs:4:10
|
4 | s2 + s3
| -- ^ -- &&str
| | |
| | `+` cannot be used to concatenate two `&str` strings
| &str
|
help: `to_owned()` can be used to create an owned `String` from a string
reference. String concatenation appends the string on the right to the string
on the left and may require reallocation. This requires ownership of the string
on the left
|
4 | s2.to_owned() + s3
| ^^^^^^^^^^^^^
Ok, fair enough. So I change my code to exactly that. But then I get this:
error[E0308]: mismatched types
--> a.rs:4:7
|
4 | s2.to_owned() + s3
| ^^^^^^^^^^^^^^^^^^
| |
| expected `&str`, found struct `std::string::String`
| help: consider borrowing here: `&(s2.to_owned() + s3)`
Ok, fair enough. So I change my code to exactly that. But then I get this:
error[E0515]: cannot return reference to temporary value
--> a.rs:4:7
|
4 | &(s2.to_owned() + s3)
| ^--------------------
| ||
| |temporary value created here
| returns a reference to data owned by the current function
Why is Rust giving bogus suggestion, and is what I am trying to do possible? Note, I would prefer to avoid suggestions such as "just use join" or similar, as this question is intended to address a more generic problem. Rust version:
rustc 1.46.0 (04488afe3 2020-08-24)
is what I am trying to do possible?
Stargazeur provided a working version in their comment: the initial value / accumulator needs to be a String rather than an &str.
Why is Rust giving bogus suggestion
Rustc doesn't have a global-enough vision so it is able to see the "detail" issue but it doesn't realise that it's really a local effect of a larger problem: fold's signature is
fn fold<B, F>(self, init: B, f: F) -> B
because you're giving fold an &str, it must ultimately return an &str, which is only possible if F just returns something it gets from "the outside", not if it creates anything internally. Since you want to create something inside your callback, the value of init is the issue.
Rustc doesn't see the conflict at that level though, because as far as it's concerned that's a perfectly valid signature e.g. you might be following a chain of things through a hashmap of returning a constant string reference for all it cares, the only real conflict it sees is between this:
F: FnMut(B, Self::Item) -> B
and the implementation of your function which doesn't actually work, so it tries to help you with that:
Rust doesn't allow adding two &str together because that would implicitly allocate a String which is the sort of hidden concern the core team would rather not hide, so Add is only implemented between String and &str, that's the first issue you see, and since that's somewhat unusual (the average language just lets you concatenate string-ish stuff or even not-at-all-strings to strings) rustc devs have added a help text noting that the LHS must be an owned String, which generally helps / works but
then the addition returns a String, so now your function doesn't match the F signature anymore: since the init is an &str that's the type of the accumlator so you need to return an &str
except if you try to create a reference to the string you've just created, you just created it inside the function, once the function returns the string will be dead and the reference left dangling, which rust can not allow
And that's how despite the best intentions, because the compiler's view is too local it guilelessly leads you down a completely useless path of frustration.
You may want to report this issue on the bug tracker (or see if it's already there). I don't know if the compiler diagnostics system would be able to grok this situation though.

How to disable "unnecessary path disambiguator" warning?

I am generating code with a macro, which contains fully qualified type paths like this:
let vec: Vec::<String>;
Note the extra :: before <String>. This is necessary so that the same input token can be also be used for the constructor, by appending ::new():
Vec::<String>::new()
However, this produces warnings:
warning: unnecessary path disambiguator
--> src/main.rs:4:17
|
4 | let vec: Vec::<String>;
| ^^ try removing `::`
I can't remove the :: because then I get an error:
error: chained comparison operators require parentheses
--> src/main.rs:6:14
|
6 | vec = Vec<String>::new();
| ^^^^^^^^^^
|
= help: use `::<...>` instead of `<...>` if you meant to specify type arguments
= help: or use `(...)` if you meant to specify fn arguments
error[E0423]: expected value, found struct `Vec`
--> src/main.rs:6:11
|
6 | vec = Vec<String>::new();
| ^^^
| |
| did you mean `vec`?
| did you mean `Vec { /* fields */ }`?
How can I disable the warning just for this one line?
It is an open issue currently.
This lint is currently slipping these attributes like #![allow(warnings)]
Reference

Primitive variable does not live long enough

There is an error in this piece of code:
let a: Vec<_> = (1..10).flat_map(|x| (1..x).map(|_| x)).collect();
The error message:
error[E0597]: `x` does not live long enough
--> src/main.rs:2:57
|
2 | let a: Vec<_> = (1..10).flat_map(|x| (1..x).map(|_| x)).collect();
| --- ^- - borrowed value needs to live until here
| | ||
| | |borrowed value only lives until here
| | borrowed value does not live long enough
| capture occurs here
But why?
Is is a primitive type, i.e. it should be cloned anyway.
What do I understand wrong?
This does not work because you capture x by reference when you do map(|_| x). x is not a variable local to the closure, so it is borrowed. To not borrow x, you must use the move keyword:
let a: Vec<_> = (1..10).flat_map(|x| (1..x).map(move |_| x)).collect();
But this is more idiomatic to write (for the same output):
use std::iter::repeat;
let b: Vec<_> = (2..10).flat_map(|x| repeat(x).take(x - 1)).collect();
Concerning the "why" question: some people could want to borrow a copyable data, so the capturing rules are the same:
Default: by reference,
With the move keyword: take the ownership.

Resources