I am writing a very simple command line calculator in rust, getting a number ,an operator, then another number and do the calculation and print the result. To show what I am getting from command args, I have printed them in a loop before the main code. I works fine for plus, minus and division, but for multiplication I get unexpected result, as I print it, instead of a star (*) for multiplication, I get the files list on my current directory.
Here is my rust code, I will appreciate an explanation and if there is any workaround.
use std::env;
fn main(){
let args: Vec<String> = env::args().collect();
for arg in args.iter(){
println!("{}", arg);
}
let mut result = 0;
let opt = args[2].to_string();
let oper1 = args[1].parse::<i32>().unwrap();
let oper2 = args[3].parse::<i32>().unwrap();
match opt.as_ref(){
"+" => result = oper1 + oper2,
"-" => result = oper1 - oper2,
"*" => result = oper1 * oper2,
"/" => result = oper1 / oper2,
_ => println!("Error")
}
println!("{} {} {} = {}", oper1, opt, oper2, result);
}
The wildcard (*) is expanding out. The shell is going to send this out to the program before it even sees what you actually typed
You can read more about here.
To avoid this, you can just wrap it in quotes, like so:
./program 1 "*" 1
Related
I got this code
val numberOfStars = urlString.length - index
val result = urlString.replaceRange(index+1..urlString.length - 1,"*") //Here
I need to repeat replacing of character with "*" by numberOfStars. Now it replace only once. And I have result like this somehere=* but I need if numberOfStars=5 the result will be somehere=*****
AFAIK there is no method out of the box .... but you can do it like
val result = urlString.replaceRange(index+1..urlString.length - 1,"*".repeat(numberOfStars))
Since you want to replace everything from index to the end of the string, it is not really necessary to use replaceRange. The following is a bit less redundant, because you do not need to write the length of the urlString twice:
val numberOfStars = urlString.length - index
val result = urlString.substring(0, index + 1) + "*".repeat(numberOfStars)
It would also be possible to do it like this:
val result = urlString.mapIndexed { i, c -> if(i > index) "*" else c }
.joinToString(separator = "")
I was reading some Rust code and I came across this line
if let Some(path) = env::args().nth(1) {
Inside of this function
fn main() {
if let Some(path) = env::args().nth(1) {
// Try reading the file provided by the path.
let mut file = File::open(path).expect("Failed reading file.");
let mut content = String::new();
file.read_to_string(&mut content);
perform_conversion(content.as_str()).expect("Conversion failed.");
} else {
println!(
"provide a path to a .cue file to be converted into a MusicBrainz compatible tracklist."
)
}
}
The line seems to be assigning the env argument to the variable path but I can't work out what the Some() around it is doing.
I took a look at the documentation for Option and I understand how it works when used on the right hand side of = but on the left hand side I am a little confused.
Am I right in thinking this line is equivalent to
if let path = Some(env::args().nth(1)) {
From the reference :
An if let expression is semantically similar to an if expression but
in place of a condition expression it expects the keyword let followed
by a refutable pattern, an = and an expression. If the value of the
expression on the right hand side of the = matches the pattern, the
corresponding block will execute, otherwise flow proceeds to the
following else block if it exists. Like if expressions, if let
expressions have a value determined by the block that is evaluated.
In here the important part is refutability. What it means refutable pattern in here it can be in different forms. For example :
enum Test {
First(String, i32, usize),
Second(i32, usize),
Third(i32),
}
You can check the x's value for a value for 3 different pattern like :
fn main() {
let x = Test::Second(14, 55);
if let Test::First(a, b, c) = x {}
if let Test::Second(a, b) = x {} //This block will be executed
if let Test::Third(a) = x {}
}
This is called refutability. But consider your code like this:
enum Test {
Second(i32, usize),
}
fn main() {
let x = Test::Second(14, 55);
if let Test::Second(a, b) = x {}
}
This code will not compile because x's pattern is obvious, it has single pattern.
You can get more information from the reference of refutability.
Also you are not right thinking for this:
if let path = Some(env::args().nth(1)) {
Compiler will throw error like irrefutable if-let pattern because as the reference says: "keyword let followed by a refutable pattern". In here there is no refutable pattern after "let". Actually this code tries to create a variable named path which is an Option and this make no sense because there is no "If" needed,
Instead Rust expects from you to write like this:
let path = Some(env::args().nth(1)); // This will be seem like Some(Some(value))
The other answers go into a lot of detail, which might be more than you need to know.
Essentially, this:
if let Some(path) = env::args().nth(1) {
// Do something with path
} else {
// otherwise do something else
}
is identical to this:
match env::args().nth(1) {
Some(path) => { /* Do something with path */ }
_ => { /* otherwise do something else */ }
}
hdiutils, when fed a correct path to a valid file, returns error 2, no such file or directory. When I join the indices of the command array with " ", print them, copy them and run the exact string in a terminal, it works fine.
This is the function edited to contain only the relevant bits. In order to reproduce my error, you will need a disk image located at ~/Downloads/StarUML.dmg.
use std::env;
use std::fs;
use std::process::Command;
fn setup_downloads(download_name: &str) {
let downloads_path: String = {
if cfg!(unix) {
//these both yield options to unwrap
let path = env::home_dir().unwrap();
let mut downloads_path = path.to_str().unwrap().to_owned();
downloads_path += "/Downloads/";
downloads_path
} else {
"we currently only support Mac OS".to_string()
}
};
let files_in_downloads =
fs::read_dir(&downloads_path).expect("the read_dir that sets files_in_downloads broke");
let mut file_path: String = "None".to_string();
for file_name in files_in_downloads {
let file_name: String = file_name
.expect("the pre string result which sets file_name has broken")
.file_name()
.into_string()
.expect("the post string result which sets file_name has broken")
.to_owned();
if file_name.contains(&download_name) {
file_path = format!("'{}{}'", &downloads_path, &file_name);
}
}
let len = file_path.len();
if file_path[len - 4..len - 1] == "dmg".to_string() {
let mount_command = ["hdiutil", "mount"];
let output = Command::new(&mount_command[0])
.arg(&mount_command[1])
.arg(&file_path)
.output()
.expect("failed to execute mount cmd");
if output.status.success() {
println!(
"command successful, returns: {}",
String::from_utf8_lossy(&output.stderr).into_owned()
);
} else {
println!(
"command failed, returns: {}",
String::from_utf8_lossy(&output.stderr).into_owned()
);
}
}
}
fn main() {
setup_downloads(&"StarUML".to_string());
}
Split your Command into a variable and print it using the debugging formatter after you have specified the arguments:
let mut c = Command::new(&mount_command[0]);
c
.arg(&mount_command[1])
.arg(&file_path);
println!("{:?}", c);
This outputs
"hdiutil" "mount" "\'/Users/shep/Downloads/StarUML.dmg\'"
Note that Command automatically provides quoting for each argument, but you have added your own set of single quotes:
format!("'{}{}'", &downloads_path, &file_name);
// ^ ^
Remove these single quotes.
I'm creating a compression/decompression library in Rust using Huffman encoding. One of the first steps is creating a data structure that contains all unique characters and the number of occurrences. I'm starting with just a simple text file, and having issues related to newline 'characters'.
My first attempt at solving this problem was constructing a BTreeMap, essentially a key-value pair of unique characters and their occurrences, respectively. Unfortunately, a newline 'character' is \n, which I think is not being handled corrected due to being two characters. I then converted the BTreeMap into a Vec to order by value, but that didn't solve the newline issue.
Here's my initial attempt at my comp binary package. Calling the binary is done using cargo, and my sample file is reproduced at the end of this question:
cargo run <text-file-in> <compressed-output-file>
main.rs
extern crate comp;
use std::env;
use std::process;
use std::io::prelude::*;
use comp::Config;
fn main() {
// Collect command-line args into a vector of strings
let mut stderr = std::io::stderr();
let config = Config::new(env::args()).unwrap_or_else(|err| {
writeln!(&mut stderr, "Parsing error: {}", err).expect("Could not write to stderr");
process::exit(1)
});
println!("Filename In: {}", config.filename_in);
println!("Filename Out: {}", config.filename_out);
if let Err(e) = comp::run(config) {
writeln!(&mut stderr, "Application error: {}", e).expect("Could not write to stderr");
process::exit(1);
}
}
lib.rs
use std::collections::btree_map::BTreeMap;
use std::error::Error;
use std::fs::File;
use std::io::Read;
use std::iter::FromIterator;
pub struct Config {
pub filename_in: String,
pub filename_out: String
}
impl Config {
pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
args.next();
let filename_in = match args.next() {
Some(arg) => arg,
None => return Err("Didn't get a filename_in string"),
};
let filename_out = match args.next() {
Some(arg) => arg,
None => return Err("Didn't get a filename_out string"),
};
Ok(Config {
filename_in: filename_in,
filename_out: filename_out,
})
}
}
pub fn run(config: Config) -> Result<(), Box<Error>> {
let mut f = File::open(config.filename_in)?;
let mut contents = String::new();
f.read_to_string(&mut contents)?;
for line in contents.lines() {
println!("{}", line);
}
// Put unique occurrences into a BTreeMap
let mut count = BTreeMap::new();
for c in contents.chars() {
*count.entry(c).or_insert(0) += 1;
}
// Put contents into a Vec to order by value
let mut v = Vec::from_iter(count);
v.sort_by(|&(_, a), &(_, b)| b.cmp(&a));
// Print key-value pair of input file
println!("Number of occurrences of each character");
for &(key, value) in v.iter() {
println!("{}: {}", key, value);
}
Ok(())
}
Sample text file, poem.txt:
I'm nobody! Who are you?
Are you nobody, too?
Then there's a pair of us — don't tell!
They'd banish us, you know.
How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!
Usage:
$ cargo run poem.txt poem
Compiling comp v0.1.0 (file:///home/chris/Projects/learn_rust/comp-rs)
Finished dev [unoptimized + debuginfo] target(s) in 1.96 secs
Running `target/debug/comp poem.txt poem`
Filename In: poem.txt
Filename Out: poem
I'm nobody! Who are you?
Are you nobody, too?
Then there's a pair of us — don't tell!
They'd banish us, you know.
How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!
Number of occurrences of each character
: 36
o: 24
e: 15
a: 10
n: 10
y: 10
< What's going on here?
: 9 < What's going on here?
r: 9
d: 8
l: 8
b: 7
i: 7
t: 7
u: 7
h: 6
s: 5
!: 4
': 4
T: 4
g: 4
m: 4
,: 3
w: 3
?: 2
H: 2
f: 2
k: 2
p: 2
.: 1
A: 1
I: 1
W: 1
c: 1
v: 1
—: 1
Unfortunately, a newline 'character' is \n, which I think is not being handled corrected due to being two characters.
No, it is not. A newline character (UTF-8 codepoint 0x0A) is a single character.
I think I need to newline character to be a key in my key-value pair, but it's currently two keys.
No, it is not. Such a thing cannot happen "accidentally" either. If we somehow had two keys, you would have to call insert twice; there's no built-in concept of a multi-key map.
All that's happening here is that a newline character is printed as... a newline!
y: 10
: 9
If you take the time to create a MCVE, you'd see this quickly:
fn main() {
let c = '\n';
println!(">{}<", c);
println!(">{:?}<", c);
}
>
<
>'\n'<
The newline character is actually an escape sequence character. This means that if you write it as \n in the code which shows up as two characters, it's actually a placeholder for a single character - a new line - and should be treated as 'one character' in the program during runtime.
The core issue you have here is that you're using println to print it out to the command line and actually printing an new line, as the \n is interpreted to mean "A new line". This is why, when you use println here, you get the behavior you see. This is typical of most languages.
While this adds a little additional bit of code, you may wish to do something like this instead to specially-handle new-line data being printed:
// Print key-value pair of input file
println!("Number of occurrences of each character");
for &(key, value) in v.iter() {
if key == '\n' {
println!("\\n": {}, value);
} else {
println!("{}: {}", key, value);
}
}
Consider as explained by Shepmaster though to create an MCVE to thoroughly test things, it helps rule out misinterpretation of what is actually happening behind the scenes.
(NOTE: I am not a Rust master; there is probably a better way to achieve the above, but this is the shortest solution I came up with in a short period of time)
I'm not entirely sure what I'm doing wrong here. I have tested the code by input and output and it functions as desired no pun intended. :'P
I'm just not setting up this function correctly and I believe it's because my arguments happen to be desirably a string. Where if done correctly "CD" will be inserted into the middle of "ABEF". So, how do I go about doing this?
Thanks!
insertstr(ABEF, CD)
Function insertstr(string1, string2)
nostrmsg = "No string"
fullng = len(string1)
half = len(string1)/2
if half>0 then hfstr1 = mid(string1, 1, half)
str2lng = len(string2)
if str2lng>0 then paste = hfstr1+string2
lshalf = mid(string1, half+1, fullng)
if str2lng+half=str2lng+half then insert = paste+lshalf
End Function
Start with the knowledge that a functions returns a value, a tentative specification of what the function should do, and a basic testing skeleton:
Option Explicit
' returns the string build by inserting m(iddle) into f(ull) at half position
Function insertInto(f, m)
insertInto = "?"
End Function
Dim t, r
For Each t In Array( _
Array("ABEF", "CD", "ABCDEF") _
)
r = insertInto(t(0), t(1))
WScript.Echo t(0), t(1), r, CStr(r = t(2))
Next
output:
cscript 26873276.vbs
ABEF CD ? False
Then learn about Left, Mid, Len, and \ (integer division).
At last, re-write insertInto() so that the result starts with
cscript 26873276.vbs
ABEF CD ABCDEF True