Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 2 years ago.
Improve this question
I am fairly new to rust and am just trying to make a hangman game to help me learn the language. I have made a for loop that checks to see if the character I entered is in a vector of all the past characters I have guessed. But when running through it just keeps asking for the letter again, after going through the debugger it juts completely skips over the for loop completely.
I am using vscode with lldb to debug.
Can anyone lend a hand to either explain to me or help me work out why it isn't working.
use rand::random;
fn take_input(pre_guessed: &mut std::vec::Vec<char>) -> char{
let mut run = true;
print!("Enter letter: ");
let mut temp_guess = String::new();
let guess_char = match std::io::stdin().read_line(&mut temp_guess){
Ok(_) => temp_guess.chars().next().expect("Could not convert guess to char"),
Err(_) => '>'
};
println!();
for i in pre_guessed.iter(){ // THIS IS THE FOR LOOP THAT IS SKIPPED OVER
if *i == guess_char{
run = false;
}
}
if run == false{
pre_guessed.push(guess_char);
guess_char
} else {
take_input(pre_guessed)
}
}
fn get_word() -> String {
// goes to a text file and gets a word
}
fn check_correct(guessed_vec: &std::vec::Vec<char>) -> bool{
let mut complete = true;
for i in guessed_vec.iter(){
if *i == '-'{
complete = false;
}
}
complete
}
fn main() {
let word_to_guess = get_word();
println!("{}", word_to_guess);
let mut encoded_word = vec!['-'; word_to_guess.len()];
let mut guessed = false;
let mut guessed_letters: std::vec::Vec<char> = Vec::new();
let mut wrong_letters: std::vec::Vec<char> = Vec::new();
while guessed == false{
println!("Guessed letters: {:?}", wrong_letters);
for i in encoded_word.iter(){
print!("{}", i);
}
println!();
let guess = take_input(&mut guessed_letters);
let mut temp_count = 0;
let mut temp_match = false;
for i in word_to_guess.chars(){
if guess == i{
encoded_word[temp_count] = i;
temp_match = true
}
temp_count += 1;
}
if temp_match == false{
wrong_letters.push(guess);
}
guessed = check_correct(&guessed_letters)
}
}
guessed_letters starts empty.
The only thing that adds to it is pre_guessed.push(guess_char) when run == false.
run starts true.
The only thing that sets it to false is when *i == guess_char for any element i of pre_guessed (which is guessed_letters).
guessed_letters can only become nonempty when run is false, and run can only become false when guessed_letters is nonempty.
It looks like you meant:
if run { // no == false
pre_guessed.push(guess_char);
guess_char
} else {
take_input(pre_guessed)
}
Related
I'm a student who is interested in learning rust. For a class project I wrote a rust script that parses a SSH log file, which specifically captured dates and IP addresses in the log.
When I first finished the project, the script took 3 minutes to run through a log file with 655147 entries. After major optimizations I got the processing down to 30 seconds. This is fine, but other students' python programs did it in 3 seconds. So I know it's definitely my fault and I want to know how to write it better. Could someone show me where I went wrong?
Here are the structs I made for reference:
struct DateLogins {
date: NaiveDate,
success: i32,
failure: i32,
}
struct IpAuth {
success: i32,
failure: i32,
first_attempt: NaiveDateTime,
successful_attempt: NaiveDateTime,
failed_reverse: bool,
break_in_attempt: bool,
ip_addr: String,
}
struct MinedReport {
start_date: NaiveDateTime,
end_date: NaiveDateTime,
total_success: i32,
total_failure: i32,
total_addrs: i32,
login_attempts: HashMap<String, DateLogins>,
unique_addrs: HashMap<String, IpAuth>,
}
And here is the main processing logic:
lazy_static! {
static ref IP_RGX: Regex = Regex::new(r"(\d{1,3}\.\d{1,3}\.\d{1,3}\d\.\d{1,3})").unwrap(); // regex for capturing the IP address of a message.
static ref LOGIN_GOOD_RGX: Regex = Regex::new(r"Accepted password").unwrap(); // regex for successful login attempts -- for total, date, and IP address.
static ref LOGIN_FAIL_RGX: Regex = Regex::new(r"Failed password").unwrap(); // regex for failed login attempts -- for total, date, and IP address
static ref REVERSE_LOOK_RGX: Regex = Regex::new(r"reverse mapping checking getaddrinfo").unwrap(); // regex for a failed reverse lookup
static ref BREAK_IN_RGX: Regex = Regex::new(r"POSSIBLE BREAK-IN ATTEMPT!").unwrap(); // regex for a break in attempt -- for IP address
}
fn main() {
let start: std::time::Instant;
let duration: std::time::Duration;
let file: File = File::open("./SSH.log").expect("Could not open log file!");
let reader: BufReader<File> = BufReader::new(file);
let origin_date: NaiveDateTime = NaiveDateTime::parse_from_str("0000 01 01 00:00:00", "%Y %m %d %H:%M:%S").expect("Could not parse start time!");
let mut report: MinedReport = MinedReport::new(origin_date.clone(), origin_date.clone());
start = Instant::now();
for line in reader.lines() {
let l = line.expect("Could not read a line!");
parse_line(l, &mut report, origin_date);
}
duration = start.elapsed();
store_log(report, "./report.txt");
println!("Total time elapsed: {:?}\n", duration);
}
// parses the values in each line
fn parse_line(line: String, report: &mut MinedReport, origin_date: NaiveDateTime) {
let time: &str = &line[..15];
let message: &str = &line[15..];
// parse the time to DateTime. Store things in the report.
let date_time: NaiveDateTime = parse_time(time, report, origin_date).unwrap();
// parse the IP address of the line. Store things in the report.
parse_ip(message, report, origin_date, date_time);
}
// parses the time value for each line.
fn parse_time(time_cap: &str, report: &mut MinedReport, origin_date: NaiveDateTime) -> Result<NaiveDateTime, ParseError> {
// add a random year just to have a string.
let time_str: String = time_cap.to_string();
let full_date: String;
let date_time: NaiveDateTime;
let date: NaiveDate;
let d: String;
// add a year. Move to the next year if it's january.
// (I know this is a bad solution, but the only months in the log are Dec and Jan, with no year)
if &time_str[..3] == "Dec" {
full_date = format!("{}{}", "0000 ", time_cap); // No year given, set it to 0000
} else {
full_date = format!("{}{}", "0001 ", time_cap); // No year given, set it to 0001
}
// get date time and date only.
date_time = NaiveDateTime::parse_from_str(&full_date, "%Y %b %d %H:%M:%S").unwrap();
date = date_time.date();
d = date.to_string();
if report.start_date == origin_date {
report.start_date = date_time;
}
report.end_date = date_time;
if !report.login_attempts.contains_key(&d) {
report.login_attempts.insert(d, DateLogins::new(date));
}
Ok(date_time)
}
fn parse_ip(message: &str, report: &mut MinedReport, origin_date: NaiveDateTime, current_date: NaiveDateTime) {
for ip in IP_RGX.captures_iter(message) {
let new_ip: String = String::from(&ip[1]);
let ip_clone: String = new_ip.clone();
let date: NaiveDate = current_date.date();
let d: String = date.to_string();
report.total_addrs += 1;
if !report.unique_addrs.contains_key(&new_ip) {
report.unique_addrs.insert(new_ip, IpAuth::new(ip_clone.clone(), origin_date.clone(), current_date.clone()));
}
let login_date = report.login_attempts.get_mut(&d).unwrap();
let unique_ip = report.unique_addrs.get_mut(&ip_clone).unwrap();
if LOGIN_FAIL_RGX.is_match(message) {
report.total_failure += 1;
login_date.failure += 1;
unique_ip.failure += 1;
} else if LOGIN_GOOD_RGX.is_match(message) {
report.total_success += 1;
login_date.success += 1;
unique_ip.success += 1;
unique_ip.successful_attempt = current_date.clone();
} else {
if REVERSE_LOOK_RGX.is_match(message) {
unique_ip.failed_reverse = true;
}
if BREAK_IN_RGX.is_match(message) {
unique_ip.break_in_attempt = true;
}
}
}
}
Like I said I'm new to rust, and programming in general, so there may be something I just don't know about. I already switched to using a hash map from a vector, but maybe there's something better I can use? I don't know. I have also wondered if the chrono or regex crates are my issue here and maybe there's a faster alternative. Either way, thanks to anyone who tries to understand and correct my code!
On my adventure to learn Rust I decided to try and print to the cli contents of the clipboard. I've done this before in Swift so thought I would have much issues in Rust.
However I'm having a hard time printing the contents of the returned NSArray. I've spent a few hours playing around with different functions but haven't made much progress.
The Swift code I have that works:
import Foundation
import AppKit
let pasteboard = NSPasteboard.general
func reload() -> [String]{
var clipboardItems: [String] = []
for element in pasteboard.pasteboardItems! {
if let str = element.string(forType: NSPasteboard.PasteboardType(rawValue: "public.utf8-plain-text")) {
clipboardItems.append(str)
}
}
return clipboardItems;
}
// Access the item in the clipboard
while true {
let firstClipboardItem = reload()
print(firstClipboardItem);
sleep(1);
}
Here is the Rust code:
use cocoa::appkit::{NSApp, NSPasteboard, NSPasteboardReading, NSPasteboardTypeString};
use cocoa::foundation::NSArray;
fn main() {
unsafe {
let app = NSApp();
let pid = NSPasteboard::generalPasteboard(app);
let changec = pid.changeCount();
let pid_item = pid.pasteboardItems();
if pid_item.count() != 0 {
let items = &*pid_item.objectAtIndex(0);
println!("{:?}", items);
}
println!("{:?}", *pid.stringForType(NSPasteboardTypeString));
}
}
The code above produces: *<NSPasteboardItem: 0x6000021a3de0>*
EDIT:
I've made a little progress but stuck on one last bit. I've managed to get the first UTF8 char out of the clipboard.
The issue I have is if I copy the text: World the system will loop the correct amount of times for the word length but will only print the first letter, in this case W. Output below:
TEXT 'W'
TEXT 'W'
TEXT 'W'
TEXT 'W'
TEXT 'W'
The bit I'm trying to get my head around is how to move to the next i8. I can't seem to find a way to point to the next i8.
The NSString function UTF8String() returns *const i8. I'm scratching my head with how one would walk the text.
use cocoa::appkit::{NSApp, NSPasteboard, NSPasteboardTypeString};
use cocoa::foundation::{NSArray, NSString};
fn main() {
unsafe {
let app = NSApp();
let pid = NSPasteboard::generalPasteboard(app);
let changec = pid.changeCount();
let nsarray_ptr = pid.pasteboardItems();
if nsarray_ptr.count() != 0 {
for i in 0..NSArray::count(nsarray_ptr) {
let raw_item_ptr = NSArray::objectAtIndex(nsarray_ptr, i);
let itm = raw_item_ptr.stringForType(NSPasteboardTypeString);
for u in 0..itm.len() {
let stri = itm.UTF8String();
println!("TEXT {:?}", *stri as u8 as char);
}
}
}
}
}
To everyone who's looked/commented on this so far thank you.
After reading some tests provided by cocoa I figured out what I needed to do.
The code below prints the contents of the clipboard. Thanks to those who pointed me in the right direction.
use cocoa::appkit::{NSApp, NSPasteboard, NSPasteboardTypeString};
use cocoa::foundation::{NSArray, NSString};
use std::{str, slice};
fn main() {
unsafe {
let app = NSApp();
let pid = NSPasteboard::generalPasteboard(app);
let nsarray_ptr = pid.pasteboardItems();
if nsarray_ptr.count() != 0 {
for i in 0..NSArray::count(nsarray_ptr) {
let raw_item_ptr = NSArray::objectAtIndex(nsarray_ptr, i);
let itm = raw_item_ptr.stringForType(NSPasteboardTypeString);
let stri = itm.UTF8String() as *const u8;
let clipboard = str::from_utf8(slice::from_raw_parts(stri, itm.len()))
.unwrap();
println!("{}", clipboard);
}
}
}
}
So, I'm trying to do a exhaustive search of a hash. The hash itself is not important here. As I want to use all processing power of my CPU, I'm using Rayon to get a thread pool and lots of tasks. The search algorithm is the following:
let (tx, rx) = mpsc::channel();
let original_hash = String::from(original_hash);
rayon::spawn(move || {
let mut i = 0;
let mut iter = SenhaIterator::from(initial_pwd);
while i < max_iteracoes {
let pwd = iter.next().unwrap();
let clone_tx = tx.clone();
rayon::spawn(move || {
let hash = calcula_hash(&pwd);
clone_tx.send((pwd, hash)).unwrap();
});
i += 1;
}
});
let mut last_pwd = None;
let bar = ProgressBar::new(max_iteracoes as u64);
while let Ok((pwd, hash)) = rx.recv() {
last_pwd = Some(pwd);
if hash == original_hash {
bar.finish();
return last_pwd.map_or(ResultadoSenha::SenhaNaoEncontrada(None), |s| {
ResultadoSenha::SenhaEncontrada(s)
});
}
bar.inc(1);
}
bar.finish();
ResultadoSenha::SenhaNaoEncontrada(last_pwd)
Just a high level explanation: as the tasks go completing their work, they send a pair of (password, hash) to the main thread, which will compare the hash with the original hash (the one I'm trying to find a password for). If they match, great, I return to main with an enum value that indicates success, and the password that produces the original hash. After all iterations end, I'll return to main with an enum value that indicates that no hash was found, but with the last password, so I can retry from this point in some future run.
I'm trying to use Indicatif to show a progress bar, so I can get a glimpse of the progress.
But my problem is that the program is growing it's memory usage without a clear reason why. If I make it run with, let's say, 1 billion iterations, it goes slowly adding memory until it fills all available system memory.
But when I comment the line bar.inc(1);, the program behaves as expected, with normal memory usage.
I've created a test program, with Rayon and Indicatif, but without the hash calculation and it works correctly, no memory misbehavior.
That makes me think that I'm doing something wrong with memory management in my code, but I can't see anything obvious.
I found a solution, but I'm still not sure why it solves the original problem.
What I did to solve it is to transfer the progress code to the first spawn closure. Look at lines 6 and 19 below:
let (tx, rx) = mpsc::channel();
let original_hash = String::from(original_hash);
rayon::spawn(move || {
let mut bar = ProgressBar::new(max_iteracoes as u64);
let mut i = 0;
let mut iter = SenhaIterator::from(initial_pwd);
while i < max_iteracoes {
let pwd = iter.next().unwrap();
let clone_tx = tx.clone();
rayon::spawn(move || {
let hash = calcula_hash(&pwd);
clone_tx.send((pwd, hash)).unwrap();
});
i += 1;
bar.inc();
}
bar.finish();
});
let mut latest_pwd = None;
while let Ok((pwd, hash)) = rx.recv() {
latest_pwd = Some(pwd);
if hash == original_hash {
return latest_pwd.map_or(PasswordOutcome::PasswordNotFound(None), |s| {
PasswordOutcome::PasswordFound(s)
})
}
}
PasswordOutcome::PasswordNotFound(latest_pwd)
That first spawn closure has the role of fetching the next password to try and pass it over to a worker task, which calculates the corresponding hash and sends the pair (password, hash) to the main thread. The main thread will wait for the pairs from the rx channel and compare with the expected hash.
What is still missing from me is why tracking the progress on the outer thread leaks memory. I couldn't identify what is really leaking. But it's working now and I'm happy with the result.
I have a newbie question about ownership, I'm trying to update (+= 1) on the last bytes and print out the UTF-8 characters.
But I have mutable borrow to the String s in order to change the last byte thus I can't print it (using immutable borrow).
What would be the Rustacean way to do so?
Note: I'm aware I'm not doing it properly, I'm at learning stage, thanks.
fn main() {
let s = vec![240, 159, 140, 145];
let mut s = unsafe {
String::from_utf8_unchecked(s)
};
unsafe {
let bytes = s.as_bytes_mut(); // mutable borrow occurs here
for _ in 0..7 {
println!("{}", s); // Crash here as immutable borrow occurs here
bytes[3] += 1;
}
}
println!("{}", s);
}
You can use std::str::from_utf8 to make a &str from bytes to print it as a string.
I'm opening a CSV file and reading it using BufReader and splitting each line into a vector. Then I try to insert or update the count in a HashMap using a specific column as key.
let mut map: HashMap<&str, i32> = HashMap::new();
let reader = BufReader::new(input_file);
for line in reader.lines() {
let s = line.unwrap().to_string();
let tokens: Vec<&str> = s.split(&d).collect(); // <-- `s` does not live long enough
if tokens.len() > c {
println!("{}", tokens[c]);
let count = map.entry(tokens[c].to_string()).or_insert(0);
*count += 1;
}
}
The compiler kindly tells me s is shortlived. Storing from inside a loop a borrowed value to container in outer scope? suggests "owning" the string, so I tried to change
let count = map.entry(tokens[c]).or_insert(0);
to
let count = map.entry(tokens[c].to_string()).or_insert(0);
but I get the error
expected `&str`, found struct `std::string::String`
help: consider borrowing here: `&tokens[c].to_string()`
When I prepend ampersand (&) the error is
creates a temporary which is freed while still in use
note: consider using a `let` binding to create a longer lived
There is some deficiency in my Rust knowledge about borrowing. How can I make the hashmap own the string passed as key?
The easiest way for this to work is for your map to own the keys. This means that you must change its type from HasMap<&str, i32> (which borrows the keys) to HashMap<String, i32>. At which point you can call to_string to convert your tokens into owned strings:
let mut map: HashMap<String, i32> = HashMap::new();
let reader = BufReader::new(input_file);
for line in reader.lines() {
let s = line.unwrap().to_string();
let tokens:Vec<&str> = s.split(&d).collect();
if tokens.len() > c {
println!("{}", tokens[c]);
let count = map.entry(tokens[c].to_string()).or_insert(0);
*count += 1;
}
}
Note however that this means that tokens[c] will be duplicated even if it was already present in the map. You can avoid the extra duplication by trying to modify the counter with get_mut first, but this requires two lookups when the key is missing:
let mut map: HashMap<String, i32> = HashMap::new();
let reader = BufReader::new(input_file);
for line in reader.lines() {
let s = line.unwrap().to_string();
let tokens:Vec<&str> = s.split(&d).collect();
if tokens.len() > c {
println!("{}", tokens[c]);
if let Some (count) = map.get_mut (tokens[c]) {
*count += 1;
} else {
map.insert (tokens[c].to_string(), 1);
}
}
}
I don't know of a solution that would only copy the key when there was no previous entry but still do a single lookup.