Associated constants in condition of constant ìf`-expression - rust

I am trying to use associated constants as a condition in an if-expression to initialize another constant. I think that this should work, as I can use the associated constants directly to initialize some other constant, so it is applicable in a const context and the if expression does not depend on any other values.
trait C {
const c: i32;
}
trait StaticAssert<T1: C, T2: C> {
const canUseAssociatedConst: i32 = T1::c;
const canCompareAssociatedConst: bool = T1::c == T2::c;
const check: i32 = if T1::c == T2::c { 1 } else { 0 };
}
When I compile this, I get an error:
error[E0019]: constant contains unimplemented expression type
--> src/lib.rs:9:24
|
9 | const check: i32 = if T1::c == T2::c { 1 } else { 0 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I am not sure what the compiler wants to tell me. I've added i32 suffixes to enforce that the literals are actually i32 values to prevent any issues from different types in the branches, but this did not help either.

As far as I know, if and others are not (yet) supported in const contexts.
However, often you can acchieve a similar effect along the lines of the following:
trait C {
const c: i32;
}
trait StaticAssert<T1:C, T2:C> {
const canUseAssociatedConst: i32 = T1::c;
const canCompareAssociatedConst: bool = T1::c == T2::c;
const check: i32 = (T1::c == T2::c) as i32;
}

Related

How can I implement the typestate pattern based on a discriminator? [duplicate]

I was wondering if it was possible to return different types depending on the conditions in the function:
This code will work if you remove '|| bool' and the 'if/else' statements.
Thanks in advance.
fn main() {
let vector: Vec<i32> = vec![0, 2, 5, 8, 9];
let targetL i32 = 3;
let found_item = linear_search(vector, target);
println!("{}", &found_item);
}
fn linear_search(vector: Vec<i32>, target: i32) -> i32 || bool {
let mut found: i32 = 0;
for item in vector {
if item == target {
found = item;
break
}
}
if found == 0 {
false
} else {
found
}
}
The precise type must be known at compile time (and is subsequently erased). You cannot decide arbitrarily which types to return at runtime.
However, you can do you've tried to do, by wrapping the types into a generic enum (which replaces the || in your code):
enum TypeOr<S, T> {
Left(S),
Right(T),
}
fn linear_search(vector: ...) -> TypeOr<i32, bool> { //...
The downside is that you must unwrap the value from the enum before you can do anything else with the result. However, this isn't so arduous in practice.
This is essentially a generalised version of the commonly used Option and Result types.
Edit: In fact, in your case, you are served very nicely by the semantics of the Option type: you never return true, so you may equate the None result with the false result your function returns, and this captures the idea you're trying to express: either your linear search finds the target and returns it (Some(found)), or it does not, and has nothing to return (None).

Why doesn't vec![[EMPTY; SIZEY]; SIZEX] produce the type Vec<[[i32; SIZEY]; SIZEX]>?

This program is fine:
const EMPTY: i32 = 0;
const SIZEX: usize = 4;
const SIZEY: usize = 4;
fn main() {
let mut test = vec![[EMPTY; SIZEY]; SIZEX];
test[2][3] = 4;
// test.what();
println!("Hello, world:{}", test[2][3]);
}
When I uncomment the test.what() line to see the type of test, the compiler emits the error:
error[E0599]: no method named `what` found for struct `Vec<[i32; 4]>` in the current scope
--> src/main.rs:8:10
|
8 | test.what();
| ^^^^ method not found in `Vec<[i32; 4]>`
I was expecting that the type of test would be something like Vec<[[i32; SIZEY]; SIZEX]>. What am I missing?
The vec![t; N] macro will make a Vec<T> (where T is the type of t) of length N. The length isn't part of the type since it is dynamic.

Parse String to most appropriate type automatically

How to parse some string to most appropriate type?
I know there is .parse::<>() method, but you need to specify type in advance like this:
fn main() {
let val = String::from("54");
assert_eq!(val.parse::<i32>().unwrap(), 54i32);
let val = String::from("3.14159");
assert_eq!(val.parse::<f32>().unwrap(), 3.14159f32);
let val = String::from("Hello!");
assert_eq!(val.parse::<String>().unwrap(), "Hello!".to_string());
}
But I need something like this:
fn main() {
let val = String::from("54");
assert_eq!(val.generic_parse().unwrap(), 54i32); // or 54i16 or 54 u32 or etc ...
let val = String::from("3.14159");
assert_eq!(val.generic_parse().unwrap(), 3.14159f32);
let val = String::from("Hello!");
assert_eq!(val.generic_parse().unwrap(), "Hello!".to_string());
}
Is there an appropriate crate for something like this? I don't want to re-invent the wheel for the umpteenth time.
EDIT
This is what I actually want to do:
struct MyStruct<T> {
generic_val: T,
}
fn main() {
let val = String::from("54");
let b = MyStruct {generic_val: val.parse().unwrap()};
let val = String::from("3.14159");
let b = MyStruct {generic_val: val.parse().unwrap()};
}
Error:
error[E0282]: type annotations needed for `MyStruct<T>`
--> src/main.rs:7:13
|
7 | let b = MyStruct {generic_val: val.parse().unwrap()};
| - ^^^^^^^^ cannot infer type for type parameter `T` declared on the struct `MyStruct`
| |
| consider giving `b` the explicit type `MyStruct<T>`, where the type parameter `T` is specified
You need to base things on the right Enum type and implement FromStr for it. Like this.
#[derive(PartialEq, Debug)]
enum Val {
Isize(isize),
F64(f64),
}
impl core::str::FromStr for Val {
type Err = & 'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match (s.parse::<isize>(), s.parse::<f64>()) {
(Ok(i),_) => Ok(Val::Isize(i)),
(Err(_), Ok(f)) => Ok(Val::F64(f)),
(Err(_), Err(_)) => Err("neither parser worked"),
}
}
}
fn main() {
assert_eq!("34".parse(), Ok(Val::Isize(34)));
assert_eq!("12.3".parse(), Ok(Val::F64(12.3)));
assert!("wrong".parse::<Val>().is_err());
}
Rust is a statically typed language. This means that the compiler needs to know the type of variables at compile time. There are three ways things can go from there:
If your strings are known at compile-time, then you might as well replace them with literal values in your code (eg. "54" → 54).
If you have some other way of knowing at compile time what type a given string should parse to, then you can specify the appropriate type when parsing the string: let a = "54".parse::<i32>().unwrap()
If your strings are only known at run-time and you want to autodetect the type, then you need to use some kind of enumerated value that will store the type alongside the value in your program:
use std::str::FromStr;
enum Value {
I32 (i32),
F32 (f32),
String (String),
}
impl Value {
fn new (s: &str) -> Value {
if let Ok (v) = s.parse::<i32>() {
Value::I32 (v)
} else if let Ok (v) = s.parse::<f32>() {
Value::F32 (v)
} else {
Value::String (s.into())
}
}
}
That way, the rest of your code will have a way of knowing what type was detected and to adjust its processing accordingly.

What is an alternative for conditional evaluation like if or match in a Rust const function?

I have code which reads a config.toml file based on the environment name and provides all the config settings to the entire project.
const fn get_conf() -> toml::Value {
let file_name = match env::var("ENVIRONMENT") {
Ok(val) => val.to_lowercase(),
Err(_e) => "development".to_lowercase(),
};
let content = fs::read_to_string(format!("conf/{}.toml", file_name)).unwrap();
let conf: Value = toml::from_str(content.as_str()).unwrap();
conf
}
static mut CONFIG: toml::Value = get_conf();
I get an error:
error[E0658]: `match` is not allowed in a `const fn`
--> src/lib.rs:2:21
|
2 | let file_name = match env::var("ENVIRONMENT") {
| _____________________^
3 | | Ok(val) => val.to_lowercase(),
4 | | Err(_e) => "development".to_lowercase(),
5 | | };
| |_____^
|
= note: see issue #49146 <https://github.com/rust-lang/rust/issues/49146> for more information
This is solved in Rust nightly, but I don't want to use a nightly build for production. Is there any workaround for using a match or if condition in a const function?
From Rust 1.46 some core language features are now allowed in const fn.
const fn improvements
There are several core language features you can now use in a const
fn:
if, if let, and match
while, while let, and loop
the && and || operators
The primary alternative for conditionals in const expressions is to cast booleans into usizes and use them as an index into an array of resulting values:
const fn demo(is_enabled: bool) -> i32 {
let choices = [
0, // false
42, // true
];
choices[is_enabled as usize]
}
This can be (painfully) expanded to more and more conditions:
const fn sign(count: i32) -> i32 {
let is_pos = count > 0;
let is_zero = count == 0;
[[-1, 0], [1, 0]][is_pos as usize][is_zero as usize]
}
fn main() {
dbg!(sign(-2));
dbg!(sign(-0));
dbg!(sign(2));
}
See also:
Calculating maximum value of a set of constant expressions at compile time
How do I convert a boolean to an integer in Rust?
For your actual use case, you should just use a lazy global value:
use once_cell::sync::Lazy; // 1.4.0
use std::{env, fs};
static mut CONFIG: Lazy<toml::Value> = Lazy::new(|| {
let file_name = match env::var("ENVIRONMENT") {
Ok(val) => val.to_lowercase(),
Err(_e) => "development".to_lowercase(),
};
let content = fs::read_to_string(format!("conf/{}.toml", file_name)).unwrap();
toml::from_str(content.as_str()).unwrap()
});
See also:
How do I create a global, mutable singleton?
It is not possible to use a const-fn with branching on stable yet indeed. Moreover, you are using env::var which is not const-fn, thus cannot be used in other const-fn. I believe you meant std::env! instead, which retrieves an environment variable during compile-time, rather than execution.
Fow now one possible "workaround" is a build script, it's a little bit clumsy yet powerful. With this approach flow would be as following:
// build.rs
use std::path::Path;
use std::env;
use std::fs::File;
fn main() {
let path = Path::new(env::var("OUT_DIR").unwrap().as_str()).join("gen.rs");
File::create(path)
.expect("gen.rs create failed")
.write_all("<your generated content here>")
.expect("gen file write failed")
}
Then you can use generated file with a direct file-include:
// main.rs or any other file
include!(concat!(env!("OUT_DIR"), "/gen.rs"));

Can I combine variable assignent with an if?

I have this code:
let fd = libc::creat(path, FILE_MODE);
if fd < 0 {
/* error */
}
the equivalent in C is shorter:
if ((fd = creat(path, FILE_MODE)) < 0) {
/* error */
}
can I do a similar thing in Rust? I tried to map it to if let but it looks like handling Options.
No, it's not possible by design. let bindings are one of the two non-expression statements in Rust. That means that the binding does not return any value that could be used further.
Bindings as expressions don't make a whole lot of sense in Rust in general. Consider let s = String::new(): this expression can't return String, because s owns the string. Or what about let (x, _) = get_tuple()? Would the expression return the whole tuple or just the not-ignored elements? So ⇒ let bindings aren't expressions.
About the if let: Sadly that won't work either. It just enables us to test if a destructuring works or to put it in other words: destructure a refutable pattern. This doesn't only work with Option<T>, but with all types.
If you really want to shorten this code, there is a way: make c_int easily convertible into a more idiomatic type, like Result. This is best done via extension trait:
trait LibcIntExt {
fn to_res(self) -> Result<u32, u32>;
}
impl LibcIntExt for c_int {
fn to_res(self) -> Result<u32, u32> {
if self < 0 {
Err(-self as u32)
} else {
Ok(self as u32)
}
}
}
With this you can use if let in the resulting Result:
if let Err(fd) = libc::creat(path, FILE_MODE).to_res() {
/* error */
}

Resources