I'm trying to learn Rust but I'm stucked with a simple "problem".
It's a super basic program that explores modules, traits and the principles of OOP. In other languages is a straight forward thing to do, but in Rust i'm stucked with. I overcomplicated the program with string slices instead of String
, just for "fun".
Basically, it's a program that create a Car
class (struct) that inherit some functionalities from Vehicle
and StartAndStoppableVehicle
traits.
It sounds pretty simple, right?? Well, When I try to compile it, it appears this error:
omiss...items from traits can only be used if the trait is in scope
This is the question: Is there a way to import the Car class only with all the inherited funcionalities, without importing the traits manually in the main
function?
It seemed simple to me but instead...
src/cars/vehicle.rs
pub trait Vehicle {
fn turn_on(&mut self);
fn turn_off(&mut self);
fn start(&mut self);
fn stop(&mut self);
}
pub trait StartAndStoppableVehicle:Vehicle {
fn start_and_stop(&mut self);
}
src/cars/cars.rs
use core::fmt;
use super::vehicle::{StartAndStoppableVehicle, Vehicle};
pub enum CarStatus {
On,
Off,
Started,
Stopped,
StartedAndStopped
}
impl fmt::Display for CarStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CarStatus::On => write!(f, "On"),
CarStatus::Off => write!(f, "Off"),
CarStatus::Started => write!(f, "Started"),
CarStatus::Stopped => write!(f, "Stopped"),
CarStatus::StartedAndStopped => write!(f, "StartedAndStopped")
}
}
}
pub struct Car<'a> {
brand: &'a str,
model: &'a str,
pub complete_name: String,
pub status: CarStatus
}
impl<'a> Car<'a> {
pub fn new(brand: &'a str, model: &'a str) -> Car<'a> {
Car {
brand: brand,
model: model,
complete_name: format!("{} {}", brand, model),
status: CarStatus::Off
}
}
}
impl<'a> Vehicle for Car<'a> {
fn turn_on(&mut self){
self.status = CarStatus::On;
}
fn turn_off(&mut self){
self.status = CarStatus::Off;
}
fn start(&mut self){
self.status = CarStatus::Started;
}
fn stop(&mut self){
self.status = CarStatus::Stopped;
}
}
impl<'a> StartAndStoppableVehicle for Car<'a> {
fn start_and_stop(&mut self) {
self.status = CarStatus::StartedAndStopped;
}
}
src/cars/mod.rs
pub mod car;
pub mod vehicle;
src/main.rs
mod car;
use car::car::Car;
// use car::vehicle::{StartAndStoppableVehicle, Vehicle}; <- it works only with this!
// And i dont want it
fn main() {
let brand = "Ferrari";
let model = "SF90 Stradale";
let mut car = Car::new(brand, model);
println!("{}", carplete_name);
println!("{}", car.status);
car.start();
println!("{}", car.status);
car.stop();
println!("{}", car.status);
car.start_and_stop();
println!("{}", car.status);
}
I'm trying to learn Rust but I'm stucked with a simple "problem".
It's a super basic program that explores modules, traits and the principles of OOP. In other languages is a straight forward thing to do, but in Rust i'm stucked with. I overcomplicated the program with string slices instead of String
, just for "fun".
Basically, it's a program that create a Car
class (struct) that inherit some functionalities from Vehicle
and StartAndStoppableVehicle
traits.
It sounds pretty simple, right?? Well, When I try to compile it, it appears this error:
omiss...items from traits can only be used if the trait is in scope
This is the question: Is there a way to import the Car class only with all the inherited funcionalities, without importing the traits manually in the main
function?
It seemed simple to me but instead...
src/cars/vehicle.rs
pub trait Vehicle {
fn turn_on(&mut self);
fn turn_off(&mut self);
fn start(&mut self);
fn stop(&mut self);
}
pub trait StartAndStoppableVehicle:Vehicle {
fn start_and_stop(&mut self);
}
src/cars/cars.rs
use core::fmt;
use super::vehicle::{StartAndStoppableVehicle, Vehicle};
pub enum CarStatus {
On,
Off,
Started,
Stopped,
StartedAndStopped
}
impl fmt::Display for CarStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CarStatus::On => write!(f, "On"),
CarStatus::Off => write!(f, "Off"),
CarStatus::Started => write!(f, "Started"),
CarStatus::Stopped => write!(f, "Stopped"),
CarStatus::StartedAndStopped => write!(f, "StartedAndStopped")
}
}
}
pub struct Car<'a> {
brand: &'a str,
model: &'a str,
pub complete_name: String,
pub status: CarStatus
}
impl<'a> Car<'a> {
pub fn new(brand: &'a str, model: &'a str) -> Car<'a> {
Car {
brand: brand,
model: model,
complete_name: format!("{} {}", brand, model),
status: CarStatus::Off
}
}
}
impl<'a> Vehicle for Car<'a> {
fn turn_on(&mut self){
self.status = CarStatus::On;
}
fn turn_off(&mut self){
self.status = CarStatus::Off;
}
fn start(&mut self){
self.status = CarStatus::Started;
}
fn stop(&mut self){
self.status = CarStatus::Stopped;
}
}
impl<'a> StartAndStoppableVehicle for Car<'a> {
fn start_and_stop(&mut self) {
self.status = CarStatus::StartedAndStopped;
}
}
src/cars/mod.rs
pub mod car;
pub mod vehicle;
src/main.rs
mod car;
use car::car::Car;
// use car::vehicle::{StartAndStoppableVehicle, Vehicle}; <- it works only with this!
// And i dont want it
fn main() {
let brand = "Ferrari";
let model = "SF90 Stradale";
let mut car = Car::new(brand, model);
println!("{}", carplete_name);
println!("{}", car.status);
car.start();
println!("{}", car.status);
car.stop();
println!("{}", car.status);
car.start_and_stop();
println!("{}", car.status);
}
Share
Improve this question
edited Mar 25 at 23:06
John Kugelman
362k69 gold badges552 silver badges596 bronze badges
asked Mar 25 at 19:58
Alessandro CorradiniAlessandro Corradini
5011 gold badge9 silver badges29 bronze badges
1
- 5 You're making a mistake trying to force OOP mind into Rust. – Chayim Friedman Commented Mar 25 at 21:25
3 Answers
Reset to default 4Unless you want to call the trait methods with fully-qualified associated function call syntax (car::vehicle::Vehicle::stop(&mut car)
), you need the use
line that you say you don't want. The rule is that you can only call trait functions with method syntax if that trait is in scope. This is necessary otherwise someone adding a trait anywhere in any crate you use has the potential to suddenly inject into your scope new methods on potentially any type. One of the benefits of requiring that you import traits you use is that the use
directives show all possible places (excluding the Rust prelude) where additional methods could come from.
One workaround could be to include the trait methods on the implementing type as well, e.g. have:
impl Car<'_> {
fn stop(&mut self) {
Vehicle::stop(self)
}
// And so on...
}
Obviously this would be a lot of boilerplate to save you from simply importing the traits you're trying to use.
My suggestion would be to work with the grain instead of against it -- accept that you need to import the trait with use
. Anything else I would call unidiomatic Rust.
Rust doesn’t allow methods from traits to be automatically available just because the struct implements those traits.
In your example, the traits need to be explicitly imported into the scope of your main
function to use the methods defined in them.
Re-exporting the traits with the Car
struct allows you to bundle the Car
struct and the relevant traits into a single module that is used in your main.rs
main.rs
mod cars;
use cars::{Car, Vehicle, StartAndStoppableVehicle}; // Single scope for everything
fn main() {
let brand = "Ferrari";
let model = "SF90 Stradale";
let mut car = Car::new(brand, model);
println!("{}", carplete_name);
println!("{}", car.status);
car.start();
println!("{}", car.status);
car.stop();
println!("{}", car.status);
car.start_and_stop();
println!("{}", car.status);
}
car.rs
use core::fmt;
use super::vehicle::{StartAndStoppableVehicle, Vehicle};
pub enum CarStatus {
On,
Off,
Started,
Stopped,
StartedAndStopped,
}
impl fmt::Display for CarStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CarStatus::On => write!(f, "On"),
CarStatus::Off => write!(f, "Off"),
CarStatus::Started => write!(f, "Started"),
CarStatus::Stopped => write!(f, "Stopped"),
CarStatus::StartedAndStopped => write!(f, "StartedAndStopped"),
}
}
}
pub struct Car<'a> {
brand: &'a str,
model: &'a str,
pub complete_name: String,
pub status: CarStatus,
}
impl<'a> Car<'a> {
pub fn new(brand: &'a str, model: &'a str) -> Car<'a> {
Car {
brand,
model,
complete_name: format!("{} {}", brand, model),
status: CarStatus::Off,
}
}
}
impl<'a> Vehicle for Car<'a> {
fn turn_on(&mut self) {
self.status = CarStatus::On;
}
fn turn_off(&mut self) {
self.status = CarStatus::Off;
}
fn start(&mut self) {
self.status = CarStatus::Started;
}
fn stop(&mut self) {
self.status = CarStatus::Stopped;
}
}
impl<'a> StartAndStoppableVehicle for Car<'a> {
fn start_and_stop(&mut self) {
self.status = CarStatus::StartedAndStopped;
}
}
mod.rs
pub mod car;
pub mod vehicle;
pub use car::Car; // Re-export the Car struct
pub use vehicle::{Vehicle, StartAndStoppableVehicle}; // Re-export both traits
In Rust, trait methods can only be accessed when they're in scope. This applies on whether they're user defined, or whether they come as part of the standard library, or some external crate.