Zig 0.8.0 error: values of type '(enum literal)' must be comptime known - switch-statement

In Zig 0.8.0, When switching over u8 characters to get an enum type, I encountered a strange compiler error from this code:
.op_type = switch(c1) {
'+' => .add, '-' => .sub,
'*' => .mul, '/' => .div,
'%' => .mod, '^' => .exp,
'|' => .bor, '~' => .bxor,
'&' => .band,
'<' => if (is_long) .lte else .lt,
'>' => if (is_long) .gte else .gt,
'=' => if (is_long) .eq else .nop,
'!' => if (is_long) .neq else return TokenError.NotAToken,
else => unreachable
}
The error was:
.\lib.zig:137:36: error: values of type '(enum literal)' must be comptime known
'<' => if (is_long) .lte else .lt,
^
Normally in zig, "must be comptime known" messages mean that I have left off a type signature on a runtime value, such as const x = 3;. However, there aren't signatures in a switch expression, and the compiler should know what the type is anyway because the field .op_type takes an Op type enum.
I was able to solve the issue by using a switch statement instead of an expression, which I used to assign a placeholder value. The result is atrocious:
var op_type: Op = undefined;
switch(c1) {
'+' => op_type = .add, '-' => op_type = .sub,
'*' => op_type = .mul, '/' => op_type = .div,
'%' => op_type = .mod, '^' => op_type = .exp,
'|' => op_type = .bor, '~' => op_type = .bxor,
'&' => op_type = .band,
'<' => if (is_long) {op_type = .lte;} else {op_type = .lt;},
'>' => if (is_long) {op_type = .gte;} else {op_type = .gt;},
'=' => if (is_long) {op_type = .eq ;} else {op_type = .nop;},
'!' => if (is_long) {op_type = .neq;} else return TokenError.NotAToken,
else => unreachable
}
...
... {
...
.op_type = op_type
}
The reason I'm posting this question is that I don't really understand the problem with the first implementation, and I would like to see if there is a better solution than what I came up with.

What you're experiencing is a quirk of enum literals. When you're writing .sub, at first that's an enum literal that is yet to be coerced to an actual enum type. Normally this process works transparently but in this case the type system doesn't seem to be able to "reason" through your if expressions.
It might be that this will be improved in the self-hosted compiler, but in the meantime the solution is to simply be explicit about the enum type when you encounter this problem.
Here's a simplified version of your code snippet that compiles: https://zig.godbolt.org/z/zeTnf3a67

Related

Matching on enum : First arm unexpectedly always matching

I have a wierdest case yet on my hands. I have an enum that I convert to a string. The enum provided is eg. Green so the string returned from the match is "text-success". Simple right? Turns out that the string returned is always "" regardless of how I obtain it. This makes no sense to me. Please help!
fn bootstrap_table_color (e: Color) -> String {
let s: String = match &e {
White => "".to_string(),
Blue => String::from("table-info"),
Green => "table-success".to_string(),
Yellow => "table-warning".to_string(),
Red => "table-danger".to_string(),
};
println!("bootstrap_table_color ({:?}) -> {:?}", e, s);
return s;
}
bootstrap_table_color (Blue) -> ""
bootstrap_table_color (Green) -> ""
That's because all possible values match the White variable arm.
You could see it by doing
let s: String = match &e {
White => format!("White={:?}", White),
}
The clean solution is to prefix arm values with your enum name:
let s = match &e {
Color::White => "".to_string(),
Color::Blue => String::from("table-info"),
Color::Green => "table-success".to_string(),
Color::Yellow => "table-warning".to_string(),
Color::Red => "table-danger".to_string(),
};
Another solution would be to do a use Color::*; but you would be vulnerable to typos or changes.

Automapper through relations in Entity Framework

As a newbie to automapper (v10.0.0) I'm trying to replace one of my queries. I currently use this to generate my response:
var query = from a in _context.ApprovalStatuses.AsNoTracking()
.Include(x => x.ApprovalOrder).ThenInclude(x => x.Worker)
where a.RequestId == request.Id
orderby a.ApprovalOrder.Position
let w = a.ApprovalOrder.Worker
select new RequestApprovalStatusDTO {
AssignedUtc = a.AssignedUtc,
Comments = a.Comments,
DecisionDateUtc = a.ApprovedDateUtc ?? a.RejectedDateUtc,
Email = w.Email,
Name = w.Name,
Uuid = a.Uuid
};
So I started by creating my mapping in my Profile subclass:
CreateMap<ApprovalStatus, RequestApprovalStatusDTO>()
.ForMember(x => x.DecisionDateUtc, x => x.MapFrom(y => y.ApprovedDateUtc ?? y.RejectedDateUtc))
.ForMember(x => x.Email, x => x.MapFrom(y => y.ApprovalOrder.Worker.Email))
.ForMember(x => x.Name, x => x.MapFrom(y => y.ApprovalOrder.Worker.Name));
And then I rewrote the query like so:
var query = _context.ApprovalStatuses
.Include(x => x.ApprovalOrder)
.ThenInclude(x => x.Worker)
.Where(x => x.RequestId == request.Id)
.OrderBy(x => x.ApprovalOrder.Position);
return Ok(_mapper.Map<RequestApprovalStatusDTO>(query));
At runtime, it's telling me
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
Mapping types:
Object -> RequestApprovalStatusDTO
System.Object -> BoltOn.RequestApprovalStatusDTO
lambda_method(Closure , object , RequestApprovalStatusDTO , ResolutionContext )
I understand it's telling me that it doesn't know how to convert from object, but I'm not sure why it's trying to do that since query is an IOrderedQueryable<ApprovalStatus>.
Thanks to Lucian's pointer I was able to solve it like so:
var query = _context.ApprovalStatuses
.Where(x => x.Request.Uuid == uuid)
.OrderBy(x => x.ApprovalOrder.Position);
var approvals = await _mapper.ProjectTo<RequestApprovalStatusDTO>(query).ToArrayAsync();
if (approvals.Length == 0)
return NotFound();
return Ok(approvals);

I want to de-spaghettify this function

How can I make this function better? I don't like the fact I have to use a forEach to define manipulable content. I was thinking of running a reduce on find, but It's all too confusing for me.
_replace (content, find, replace, delimiter = '', findDelimiter = '') {
if (!Array.isArray(find)) find = [find]
let result = content
find.forEach(ref => {
result = result.split(findDelimiter).reduce((a, e) => a + delimiter + (this._reference[replace][this._reference[ref].indexOf(e)] || e), '')
})
return result
}

Is there a more beautiful way to convert a character literal to its corresponding escape character?

I'm writing a parser:
match ch {
// ...
'b' => {
token.push('\b');
continue;
},
'f' => {
token.push('\f');
continue;
},
'n' => {
token.push('\n');
continue;
},
'r' => {
token.push('\r');
continue;
},
't' => {
token.push('\t');
continue;
},
// ...
},
There's a lot of repeating code, so I'm thinking about a more elegant way to do it. I thought something like this would be possible:
macro_rules! escaped_match {
($char:expr) => (
'$char' => {
token.push('\$char')
continue;
}
)
}
But my hope is gone:
error: character literal may only contain one codepoint: '$
--> src/main.rs:3:9
|
3 | '$char' => {
| ^^
Is there a more beautiful way to do it, whether using macros, compiler plugins, hacks, or black magic?
Rust macros are not C macros — you cannot create invalid tokens and hope that they are valid sometime in the future. Likewise, they aren't a fancy way of concatenating strings that later get interpreted as code.
Looking at the code, It seems like the main repetition is in the push and continue. I'd probably use normal functions and pattern matching to DRY up that specific code:
fn escape_char(c: char) -> Option<char> {
Some(match c {
// 'b' => '\b',
// 'f' => '\f',
'n' => '\n',
'r' => '\r',
't' => '\t',
_ => return None,
})
}
fn main() {
// ...
if let Some(escape) = escape_char('b') {
token.push(escape);
continue;
}
// ...
}
Now the mapping is constrained to a single x => '\y' line.
Note that \b and \f aren't recognized escape codes in Rust; not sure what you are going to do for those.

What is the correct & idiomatic way to check if a string starts with a certain character in Rust?

I want to check whether a string starts with some chars:
for line in lines_of_text.split("\n").collect::<Vec<_>>().iter() {
let rendered = match line.char_at(0) {
'#' => {
// Heading
Cyan.paint(*line).to_string()
}
'>' => {
// Quotation
White.paint(*line).to_string()
}
'-' => {
// Inline list
Green.paint(*line).to_string()
}
'`' => {
// Code
White.paint(*line).to_string()
}
_ => (*line).to_string(),
};
println!("{:?}", rendered);
}
I've used char_at, but it reports an error due to its instability.
main.rs:49:29: 49:39 error: use of unstable library feature 'str_char': frequently replaced by the chars() iterator, this method may be removed or possibly renamed in the future; it is normally replaced by chars/char_indices iterators or by getting the first char from a subslice (see issue #27754)
main.rs:49 let rendered = match line.char_at(0) {
^~~~~~~~~~
I'm currently using Rust 1.5
The error message gives useful hints on what to do:
frequently replaced by the chars() iterator, this method may be removed or possibly renamed in the future; it is normally replaced by chars/char_indices iterators or by getting the first char from a subslice (see issue #27754)
We could follow the error text:
for line in lines_of_text.split("\n") {
match line.chars().next() {
Some('#') => println!("Heading"),
Some('>') => println!("Quotation"),
Some('-') => println!("Inline list"),
Some('`') => println!("Code"),
Some(_) => println!("Other"),
None => println!("Empty string"),
};
}
Note that this exposes an error condition you were not handling! What if there was no first character?
We could slice the string and then pattern match on string slices:
for line in lines_of_text.split("\n") {
match &line[..1] {
"#" => println!("Heading"),
">" => println!("Quotation"),
"-" => println!("Inline list"),
"`" => println!("Code"),
_ => println!("Other")
};
}
Slicing a string operates by bytes and thus this will panic if your first character isn't exactly 1 byte (a.k.a. an ASCII character). It will also panic if the string is empty. You can choose to avoid these panics:
for line in lines_of_text.split("\n") {
match line.get(..1) {
Some("#") => println!("Heading"),
Some(">") => println!("Quotation"),
Some("-") => println!("Inline list"),
Some("`") => println!("Code"),
_ => println!("Other"),
};
}
We could use the method that is a direct match to your problem statement, str::starts_with:
for line in lines_of_text.split("\n") {
if line.starts_with('#') { println!("Heading") }
else if line.starts_with('>') { println!("Quotation") }
else if line.starts_with('-') { println!("Inline list") }
else if line.starts_with('`') { println!("Code") }
else { println!("Other") }
}
Note that this solution doesn't panic if the string is empty or if the first character isn't ASCII. I'd probably pick this solution for those reasons. Putting the if bodies on the same line as the if statement is not normal Rust style, but I put it that way to leave it consistent with the other examples. You should look to see how separating them onto different lines looks.
As an aside, you don't need collect::<Vec<_>>().iter(), this is just inefficient. There's no reason to take an iterator, build a vector from it, then iterate over the vector. Just use the original iterator.

Resources