Reputation: 193
I am very, very new to Rust and trying to implement some simple things to get the feel for the language. Right now, I'm stumbling over the best way to implement a class-like struct that involves casting a string to an int. I'm using a global-namespaced function and it feels wrong to my Ruby-addled brain.
What's the Rustic way of doing this?
use std::io;
struct Person {
name: ~str,
age: int
}
impl Person {
fn new(input_name: ~str) -> Person {
Person {
name: input_name,
age: get_int_from_input(~"Please enter a number for age.")
}
}
fn print_info(&self) {
println(fmt!("%s is %i years old.", self.name, self.age));
}
}
fn get_int_from_input(prompt_message: ~str) -> int {
println(prompt_message);
let my_input = io::stdin().read_line();
let my_val =
match from_str::<int>(my_input) {
Some(number_string) => number_string,
_ => fail!("got to put in a number.")
};
return my_val;
}
fn main() {
let first_person = Person::new(~"Ohai");
first_person.print_info();
}
This compiles and has the desired behaviour, but I am at a loss for what to do here--it's obvious I don't understand the best practices or how to implement them.
Edit: this is 0.8
Upvotes: 3
Views: 1758
Reputation: 16630
A variation of telotortium's input reading function that doesn't fail on bad input. The loop { ... }
keyword is preferred over writing while true { ... }
. In this case using return
is fine since the function is returning early.
fn int_from_input(prompt: &str) -> int {
println(prompt);
loop {
match from_str::<int>(io::stdin().read_line()) {
Some(x) => return x,
None => println("Oops, that was invalid input. Try again.")
};
}
}
Upvotes: 0
Reputation:
This isn't really rust-specifc, but try to split functionality into discrete units. Don't mix the low-level tasks of putting strings on the terminal and getting strings from the terminal with the more directly relevant (and largely implementation dependent) tasks of requesting a value, and verify it. When you do that, the design decisions you should make start to arise on their own.
For instance, you could write something like this (I haven't compiled it, and I'm new to rust myself, so they're probably at LEAST one thing wrong with this :) ).
fn validated_input_prompt<T>(prompt: ~str) {
println(prompt);
let res = io::stdin().read_line();
loop {
match res.len() {
s if s == 0 => { continue; }
s if s > 0 {
match T::from_str(res) {
Some(t) -> {
return t
},
None -> {
println("ERROR. Please try again.");
println(prompt);
}
}
}
}
}
}
And then use it as:
validated_input_prompt<int>("Enter a number:")
or:
validated_input_prompt<char>("Enter a Character:")
BUT, to make the latter work, you'd need to implement FromStr for chars, because (sadly) rust doesn't seem to do it by default. Something LIKE this, but again, I'm not really sure of the rust syntax for this.
use std::from_str::*;
impl FromStr for char {
fn from_str(s: &str) -> Option<Self> {
match len(s) {
x if x >= 1 => {
Option<char>.None
},
x if x == 0 => {
None,
},
}
return s[0];
}
}
Upvotes: 0
Reputation: 3541
Here is my version of the code, which I have made more idiomatic:
use std::io;
struct Person {
name: ~str,
age: int
}
impl Person {
fn print_info(&self) {
println!("{} is {} years old.", self.name, self.age);
}
}
fn get_int_from_input(prompt_message: &str) -> int {
println(prompt_message);
let my_input = io::stdin().read_line();
from_str::<int>(my_input).expect("got to put in a number.")
}
fn main() {
let first_person = Person {
name: ~"Ohai",
age: get_int_from_input("Please enter a number for age.")
};
first_person.print_info();
}
fmt!
/format!
First, Rust is deprecating the fmt!
macro, with printf
-based syntax, in favor of format!
, which uses syntax similar to Python format strings. The new version, Rust 0.9, will complain about the use of fmt!
. Therefore, you should replace fmt!("%s is %i years old.", self.name, self.age)
with format!("{} is {} years old.", self.name, self.age)
. However, we have a convenience macro println!(...)
that means exactly the same thing as println(format!(...))
, so the most idiomatic way to write your code in Rust would be
println!("{} is {} years old.", self.name, self.age);
For a simple type like Person
, it is idiomatic in Rust to create instances of the type by using the struct literal syntax:
let first_person = Person {
name: ~"Ohai",
age: get_int_from_input("Please enter a number for age.")
};
In cases where you do want a constructor, Person::new
is the idiomatic name for a 'default' constructor (by which I mean the most commonly used constructor) for a type Person
. However, it would seem strange for the default constructor to require initialization from user input. Usually, I think you would have a person
module, for example (with person::Person
exported by the module). In this case, I think it would be most idiomatic to use a module-level function fn person::prompt_for_age(name: ~str) -> person::Person
. Alternatively, you could use a static method on Person
-- Person::prompt_for_age(name: ~str)
.
&str
vs. ~str
in function parametersI've changed the signature of get_int_from_input
to take a &str
instead of ~str
. ~str
denotes a string allocated on the exchange heap -- in other words, the heap that malloc
/free
in C, or new
/delete
in C++ operate on. Unlike in C/C++, however, Rust enforces the requirement that values on the exchange heap can only be owned by one variable at a time. Therefore, taking a ~str
as a function parameter means that the caller of the function can't reuse the ~str
argument that it passed in -- it would have to make a copy of the ~str
using the .clone
method.
On the other hand, &str
is a slice into the string, which is just a reference to a range of characters in the string, so it doesn't require a new copy of the string to be allocated when a function with a &str
parameter is called.
The reason to use &str
rather than ~str
for prompt_message
in get_int_from_input
is that the function doesn't need to hold onto the message past the end of the function. It only uses the prompt message in order to print it (and println
takes a &str
, not a ~str
). Once you change the function to take &str
, you can call it like get_int_from_input("Prompt")
instead of get_int_from_input(~"Prompt")
, which avoids the unnecessary allocation of "Prompt"
on the heap (and similarly, you can avoid having to clone s
in the code below):
let s: ~str = ~"Prompt";
let i = get_int_from_input(s.clone());
println(s); // Would complain that `s` is no longer valid without cloning it above
// if `get_int_from_input` takes `~str`, but not if it takes `&str`.
Option<T>::expect
The Option<T>::expect
method is the idiomatic shortcut for the match statement you have, where you want to either return x
if you get Some(x)
or fail with a message if you get None
.
return
In Rust, it is idiomatic (following the example of functional languages like Haskell and OCaml) to return a value without explicitly writing a return
statement. In fact, the return value of a function is the result of the last expression in the function, unless the expression is followed by a semicolon (in which case it returns ()
, a.k.a. unit, which is essentially an empty placeholder value -- ()
is also what is returned by functions without an explicit return type, such as main
or print_info
).
I'm not a great expert on Rust by any means. If you want help on anything related to Rust, you can try, in addition to Stack Overflow, the #rust IRC channel on irc.mozilla.org or the Rust subreddit.
Upvotes: 8