Re-using values without declaring variables - rust

In Kotlin, I can re-use values so:
"127.0.0.1:135".let {
connect(it) ?: System.err.println("Failed to connect to $it")
}
Is anything similar possible in Rust? To avoid using a temporary variable like this:
let text_address = "127.0.0.1:135";
TcpListener::bind(text_address).expect(format!("Failed to connect to {}", text_address));

According to this reference, T.let in Kotlin is a generic method-like function which runs a closure (T) -> R with the given value T passed as the first argument. From this perspective, it resembles a mapping operation from T to R. Under Kotlin's syntax though, it looks like a means of making a scoped variable with additional emphasis.
We could do the exact same thing in Rust, but it doesn't bring anything new to the table, nor makes the code cleaner (using _let because let is a keyword in Rust):
trait LetMap {
fn _let<F, R>(self, mut f: F) -> R
where
Self: Sized,
F: FnMut(Self) -> R,
{
f(self)
}
}
impl<T> LetMap for T {}
// then...
"something"._let(|it| {
println!("it = {}", it);
"good"
});
When dealing with a single value, it is actually more idiomatic to just declare a variable. If you need to constrain the variable (and/or the value's lifetime) to a particular scope, just place it in a block:
let conn = {
let text_address = "127.0.0.1:135";
TcpListener::bind(text_address)?
};
There is also one more situation worth mentioning: Kotlin has an idiom for nullable values where x?.let is used to conditionally perform something when the value isn't null.
val value = ...
value?.let {
... // execute this block if not null
}
In Rust, an Option already provides a similar feature, either through pattern matching or the many available methods with conditional execution: map, map_or_else, unwrap_or_else, and_then, and more.
let value: Option<_> = get_opt();
// 1: pattern matching
if let Some(non_null_value) = value {
// ...
}
// 2: functional methods
let new_opt_value: Option<_> = value.map(|non_null_value| {
"a new value"
}).and_then(some_function_returning_opt);

This is similar
{
let text_address = "127.0.0.1:135";
TcpListener::bind(text_address).expect(format!("Failed to connect to {}", text_address));
}
// now text_address is out of scope

Related

Hashmap with enum values

Rust Newbie.
I'd like to create a hashmap that contains values of different types. I got as far as shown, and I can store the values, but I cannot cast them back to the original time when reading them. I'm sure I'm missing something basic, but I'm still struggling with the enum concept in Rust.
#[derive(Debug)]
struct My1 { value: i32 }
#[derive(Debug)]
struct My2 { value: String }
#[derive(Debug)]
enum MyValueType {
MyOne(Vec<My1>),
MyTwo(Vec<My2>)
}
fn main() {
use std::collections::HashMap;
let mut map: HashMap<&str, MyValueType> = HashMap::new();
let a1 = vec!(My1 { value: 100 });
let a2 = vec!(My2 { value: "onehundred".into() });
map.insert("one", MyValueType::MyOne(a1));
map.insert("two", MyValueType::MyTwo(a2));
//let b: &Vec<My1> = map.get("one").unwrap().into(); // err
for (key, value) in &map {
println!("{}: {:?}", key, value);
}
let k1: Vec<My1> = *map.get("one").unwrap().into(); // err: type annotation needed
let k2: Vec<My2> = *map.get("two").unwrap().into(); // err: type annotation needed
}
How should I implement this so I can cast the value of type MyValueType back to Vec or Vec as the case may be? Or am I fundamentally wrong on how I'm setting this up in general?
Starting with:
let v = map.get("one");
The hash map will return an option of the enum (Option<MyValueType>). After unwrapping the option, you’re left with the enum.
let v = map.get("one"); // v is MyValueType (specifically MyOne)
This enum has one of the possible values of MyOne or MyTwo, but we don’t yet know which (more specifically — the compiler doesn’t know, even if we can tell just by looking that it’s MyOne). If you want to reach in to MyOne or MyTwo and grab one of the Vecs that are stored there, you need to match against the enum. For example:
match map.get("one").unwrap() {
MyValueType::MyOne(vector) => {
// do something
},
MyValurType::MyTwo => panic!(“unexpected)
}
This intentionally forces you to check that the enum is the value you are expecting before you are able to access to the data within. Typically you won’t know the exact type of the enum when you are writing code (otherwise why use an enum!) which is why this might seem a bit verbose.

Rust: how to assign `iter().map()` or `iter().enumarate()` to same variable

struct A {...whatever...};
const MY_CONST_USIZE:usize = 127;
// somewhere in function
// vec1_of_A:Vec<A> vec2_of_A_refs:Vec<&A> have values from different data sources and have different inside_item types
let my_iterator;
if my_rand_condition() { // my_rand_condition is random and compiles for sake of simplicity
my_iterator = vec1_of_A.iter().map(|x| (MY_CONST_USIZE, &x)); // Map<Iter<Vec<A>>>
} else {
my_iterator = vec2_of_A_refs.iter().enumerate(); // Enumerate<Iter<Vec<&A>>>
}
how to make this code compile?
at the end (based on condition) I would like to have iterator able build from both inputs and I don't know how to integrate these Map and Enumerate types into single variable without calling collect() to materialize iterator as Vec
reading material will be welcomed
In the vec_of_A case, first you need to replace &x with x in your map function. The code you have will never compile because the mapping closure tries to return a reference to one of its parameters, which is never allowed in Rust. To make the types match up, you need to dereference the &&A in the vec2_of_A_refs case to &A instead of trying to add a reference to the other.
Also, -127 is an invalid value for usize, so you need to pick a valid value, or use a different type than usize.
Having fixed those, now you need some type of dynamic dispatch. The simplest approach would be boxing into a Box<dyn Iterator>.
Here is a complete example:
#![allow(unused)]
#![allow(non_snake_case)]
struct A;
// Fixed to be a valid usize.
const MY_CONST_USIZE: usize = usize::MAX;
fn my_rand_condition() -> bool { todo!(); }
fn example() {
let vec1_of_A: Vec<A> = vec![];
let vec2_of_A_refs: Vec<&A> = vec![];
let my_iterator: Box<dyn Iterator<Item=(usize, &A)>>;
if my_rand_condition() {
// Fixed to return x instead of &x
my_iterator = Box::new(vec1_of_A.iter().map(|x| (MY_CONST_USIZE, x)));
} else {
// Added map to deref &&A to &A to make the types match
my_iterator = Box::new(vec2_of_A_refs.iter().map(|x| *x).enumerate());
}
for item in my_iterator {
// ...
}
}
(Playground)
Instead of a boxed trait object, you could also use the Either type from the either crate. This is an enum with Left and Right variants, but the Either type itself implements Iterator if both the left and right types also do, with the same type for the Item associated type. For example:
#![allow(unused)]
#![allow(non_snake_case)]
use either::Either;
struct A;
const MY_CONST_USIZE: usize = usize::MAX;
fn my_rand_condition() -> bool { todo!(); }
fn example() {
let vec1_of_A: Vec<A> = vec![];
let vec2_of_A_refs: Vec<&A> = vec![];
let my_iterator;
if my_rand_condition() {
my_iterator = Either::Left(vec1_of_A.iter().map(|x| (MY_CONST_USIZE, x)));
} else {
my_iterator = Either::Right(vec2_of_A_refs.iter().map(|x| *x).enumerate());
}
for item in my_iterator {
// ...
}
}
(Playground)
Why would you choose one approach over the other?
Pros of the Either approach:
It does not require a heap allocation to store the iterator.
It implements dynamic dispatch via match which is likely (but not guaranteed) to be faster than dynamic dispatch via vtable lookup.
Pros of the boxed trait object approach:
It does not depend on any external crates.
It scales easily to many different types of iterators; the Either approach quickly becomes unwieldy with more than two types.
You can do this using a Boxed trait object like so:
let my_iterator: Box<dyn Iterator<Item = _>> = if my_rand_condition() {
Box::new(vec1_of_A.iter().map(|x| (MY_CONST_USIZE, x)))
} else {
Box::new(vec2_of_A_refs.iter().enumerate().map(|(i, x)| (i, *x)))
};
I don't think this is a good idea generally though. A few things to note:
The use of trait objects means the types here must be resolved dynamically. This adds a lot of overhead.
The closure in vec1's iterator's map method cannot reference its arguments. Instead the second map must be added to vec2s iterator. The effect of this is that all the items are being copied regardless. If you are doing this, why not collect()? The overhead for creating the Vec or whatever you choose should be less than that of the dynamic resolution.
Bit pedantic, but remember if statements are expressions in Rust, and so the assignment can be expressed a little more cleanly as I have done above.

Is there idiomatic way to handle "reference or create and use reference to it" pattern in Rust?

I realized that very often in Rust I need to do a following pattern:
let variable = &some_ref;
let variable = if something {
let new_variable = create_something();
&new_variable
} else {
variable
};
// Use variable here
In other words, I need to either use an existing reference or create a new owned value and use a reference to it.
But the problem is if I do it like in the example above the new_variable does not live long enough, it's dropped at the end of the first if clause.
Is there a idiomatic way to structure the code nicely to achieve the "use reference or create new and use reference to new" way? Or I just have to copy/make function for the code that uses variable 2 times - one for branch where I already have reference and another for branch where I create a owned value and that use reference to it?
Here is real-world example of how I usually use function (overlay_with_u32 in this case) to copy the behavior between 2 branches:
let source = &source;
if is_position_negative(x, y) {
let source = crop(source, x, y);
overlay_with_u32(destination, &source, x, y);
} else {
overlay_with_u32(destination, source, x, y);
};
You can use Cow(Clone-on-write) for this.
The Cow type is an emum of either an owned or a borrowed value:
pub enum Cow<'a, B>
where
B: 'a + ToOwned + ?Sized,
{
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned),
}
You could use it something like (variables renamed for clarity):
use std::borrow::Cow;
let variable_ref = &some_ref;
let variable = if something {
let variable_created = create_something();
Cow::Owned(variable_created)
} else {
Cow::Borrowed(variable_ref)
};
Functions that accept a &T can be given a &Cow<T>, which will automatically be deferenced as you'd expect:
let variable: Cow<'_, i32> = Cow::Owned(3);
do_stuff(&variable);
fn do_stuff(i: &i32) {
println!("{}", i);
}
Using Cow may be the right thing to do, but here I'm going to suggest another approach, that might be cheaper (especially if the types are Drop-less, in this case it is zero-cost except it may require more stack space), but requires more code and may be less obvious.
Rust allows you to declare a variable but initialize it conditionally, as long as the compiler can prove that the variable is always initialized if it is used. You can exploit this fact to longer the lifetime of a variable inside a scope; instead of:
let reference = {
let variable = ...;
&variable
};
You can write:
let variable;
let reference = {
variable = ...;
&variable
};
But now variable lives long enough.
Applied to your case, it looks like:
let variable = &some_ref;
let new_variable;
let variable = if something {
new_variable = create_something();
&new_variable
} else {
variable
};

How to avoid move of possibly-uninitialized variable for MutexGuard interface?

for following code:
let conversation_model =
if lsm { CONVMODEL.lock().await } else {
conv_model_loader()
};
CONVMODEL.lock().await is MutexGuard<T> and conv_model_loader() is just T
I need common interface for those two so I can not copy-paste my code for two situations because it will only differ with this type, anything else is the same.
Edit:
there is code ... (at least what I was trying to do)
let (locked, loaded); // pun not intended
if lsm {
locked = CONVMODEL.lock().await;
} else {
loaded = conv_model_loader();
};
let mut chat_context = CHAT_CONTEXT.lock().await;
task::spawn_blocking(move || {
let conversation_model = if lsm { &*locked } else { &loaded };
but I've fialed becuse of
use of possibly-uninitialized variable: `locked`\nuse of possibly-uninitialized `locked`
So question is really how to have MutexGuard with interface &T but use it inside spawn_blocking and also with #[async_recursion]
Edit:
let (mut locked, mut loaded) = (None, None);
if lsm {
locked = Some( CONVMODEL.lock().await );
} else {
loaded = Some( conv_model_loader() );
};
let mut chat_context = CHAT_CONTEXT.lock().await;
task::spawn_blocking(move || {
let (lock, load);
let conversation_model =
if lsm {
lock = locked.unwrap();
&*lock
} else {
load = loaded.unwrap();
&load
};
following code is working but actually very ugly XD
(I wonder if it is possible to simplify this code)
Whenever you have some set of choices for a value, you want to reach for enum. For example, in Rust we don't do things like let value: T; let is_initialized: bool;, we do Option<T>.
You have a choice of two values, either an acquired mutex or a direct value. This is typically called "either", and there is a popular Rust crate containing this type: Either. For you it might look like:
use either::Either;
let conv_model = if lsm {
Either::Left(CONVMODEL.lock().await)
} else {
Either::Right(conv_model_loader())
};
tokio::task::spawn_blocking(move || {
let conversation_model = match &conv_model {
Either::Left(locked) => locked.deref(),
Either::Right(loaded) => loaded,
};
conversation_model.infer();
});
(Full example.)
This type used to live in the standard library, but was removed because it wasn't often used as it's fairly trivial to make a more descriptive domain-specific type. I agree with that, and you might do:
pub enum ConvModelSource {
Locked(MutexGuard<'static, ConvModel>),
Loaded(ConvModel),
}
impl Deref for ConvModelSource {
type Target = ConvModel;
fn deref(&self) -> &Self::Target {
match self {
Self::Locked(guard) => guard.deref(),
Self::Loaded(model) => model,
}
}
}
// ...
let conv_model = if lsm {
ConvModelSource::Locked(CONVMODEL.lock().await)
} else {
ConvModelSource::Loaded(conv_model_loader())
};
tokio::task::spawn_blocking(move || {
conv_model.infer();
});
(Full example.)
This is much more expressive, and moves the "how to populate this" away from where it's used.
In the common case you do want to use the simpler approach user4815162342 showed. You will store one of the temporaries, form a reference to it (knowing you just initialized it), and hand that back.
This doesn't work with spawn_blocking, however. The lifetime of the reference is that of the temporaries - handing such a reference off to a spawned task is a dangling reference.
This is why the error messages (of the form "borrowed value does not live long enough" and "argument requires that locked is borrowed for 'static") guided you to go down the path of trying to move locked and loaded into the closure to be in their final resting place, then form a reference. Then the reference wouldn't be dangling.
But then this implies you move a possibly-uninitialized value into the closure. Rust does not understand you are using an identical check to see which temporary value is populated. (You could imagine a typo on the second check doing !lsm and now you're switched up.)
Ultimately, you have to move the source of the value into the spawned task (closure) so that you form references with usable lifetimes. The use of enum is basically codifying your boolean case check into something Rust understands and will unpack naturally.
You can extract &mut T from both and use that. Something like the following should work:
let (locked, loaded); // pun not intended
let conversation_model = if lsm {
locked = CONVMODEL.lock().await;
&mut *locked
} else {
loaded = conv_model_loader();
&mut loaded
};

A built-in Object in Rust

Rust doesn't have built-in Object type I take it? If so, how do I, say, create a HashMap of "something" that in Java would be Object:
fn method1(my_hash_map: HashMap<&str, ???>) { ... } // Rust
void method1(Map<String, Object> myMap) { ... } // Java
If you want a HashMap that can mix values of many different types, you'll have to use Any. The most direct equivalent to Map<String, Object> would be HashMap<String, Box<Any>>. I switched &str to String because &str without a lifetime is probably not what you want and in any case even further removed from Java String than Rust's String already is.
However, if you simply don't care about the type of the values, it's simpler and more efficient to make method1 generic:
fn method1<T>(my_hash_map: HashMap<String, T>) { ... }
Of course, you can also add constraints T:Trait to do more interesting things with the values (cf. Object allows equality comparisons and hashing).
To expand on rightføld's comment, Any is the closest you can really get in Rust, though it does come with a major restriction: it is only implemented by types which satisfy the 'static lifetime; that is, you can't treat any type which contains non-static references as an Any.
A second complication is that Object in Java has reference semantics and gives you shared ownership. As such, you'd need something like Rc<RefCell<Any>> to get something roughly comparable. Note, however, that this is heavily discouraged since it basically moves a lot of checks to runtime. Something like this should be a fallback of last resort.
Finally, note that, insofar as I'm aware, there's no way to do a dynamic upcast on an Any to anything other than the erased type; so you can't take a reference to a value that, say, implements Show, turn it into an &Any, and then upcast to a &Show.
Better alternatives, if applicable, include generalising the value type (so use generic functions and structs), using an enum if there is a fixed, finite list of types you want to support, or write and implement a custom trait, in that order.
To give you an example of working with Any, however, I threw the following together. Note that we have to try explicitly upcasting to every supported type.
#![feature(if_let)]
use std::any::{Any, AnyRefExt};
use std::collections::HashMap;
fn main() {
let val_a = box "blah";
let val_b = box 42u;
let val_c = box 3.14159f64;
let mut map = HashMap::new();
map.insert("a".into_string(), val_a as Box<Any>);
map.insert("b".into_string(), val_b as Box<Any>);
map.insert("c".into_string(), val_c as Box<Any>);
println!("{}", map);
splang(&map);
}
fn splang(map: &HashMap<String, Box<Any>>) {
for (k, v) in map.iter() {
if let Some(v) = v.downcast_ref::<&str>() {
println!("[\"{}\"]: &str = \"{}\"", k, *v);
} else if let Some(v) = v.downcast_ref::<uint>() {
println!("[\"{}\"]: uint = {}", k, *v);
} else {
println!("[\"{}\"]: ? = {}", k, v);
}
}
}
When run, it outputs:
{c: Box<Any>, a: Box<Any>, b: Box<Any>}
["c"]: ? = Box<Any>
["a"]: &str = "blah"
["b"]: uint = 42

Resources