Reputation: 2827
I am rather new to rust, so the following might also be a misunderstanding of a concept: I took the ggez basic project template from the basic project template which looks like this:
use ggez::{graphics, Context, ContextBuilder, GameResult};
use ggez::event::{self, EventHandler};
fn main() {
// Make a Context.
let (mut ctx, mut event_loop) = ContextBuilder::new("my_game", "Cool Game Author")
.build()
.expect("aieee, could not create ggez context!");
// Create an instance of your event handler.
// Usually, you should provide it with the Context object to
// use when setting your game up.
let mut my_game = MyGame::new(&mut ctx);
// Run!
match event::run(&mut ctx, &mut event_loop, &mut my_game) {
Ok(_) => println!("Exited cleanly."),
Err(e) => println!("Error occured: {}", e)
}
}
struct MyGame {
// Your state here...
}
impl MyGame {
pub fn new(_ctx: &mut Context) -> MyGame {
// Load/create resources such as images here.
MyGame {
// ...
}
}
}
impl EventHandler for MyGame {
fn update(&mut self, _ctx: &mut Context) -> GameResult<()> {
// Update code here...
Ok(())
}
fn draw(&mut self, ctx: &mut Context) -> GameResult<()> {
graphics::clear(ctx, graphics::WHITE);
// Draw code here...
graphics::present(ctx)
}
}
And refactored it into two files main.rs
and game.rs
The first just contained the main()
function, everything else goes into game.rs
.
After changing the import in main.rs
to
mod game;
use game::MyGame;
use ggez::{graphics, Context, ContextBuilder, GameResult};
use ggez::event::{self, EventHandler};
and adding this to game.rs
use ggez::event::EventHandler;
use ggez::{Context, GameResult, graphics};
everything works provided I make MyGame
public.
However, now the refactoring is making a huge change, as MyGame had been private before.
I tried several approaches with pub (in infinite_runner::main)
and similar, like crate::main
but none was accepted with various error messages.
Now my question is, is there a way of exposing MyGame
to main.rs
without exposing it to anyone else? It seems like main is not a valid target.
Upvotes: 2
Views: 829
Reputation: 2827
I managed to figure it out, by reiterating through the rust documentation. Although it feels a little fishy to answer my own question it might actually be helpful for others:
TLDR; We want the code inside the main function to have access to MyGame but any other module outside denied access.
Can it be done with factoring out the code to game.rs
: No.
Can it be done at all: Yes.
Here is why and how:
From the rust documentation Visibility and Privacy:
With the notion of an item being either public or private, Rust allows item accesses in two cases:
- If an item is public, then it can be accessed externally from some module m if you can access all the item's parent modules from m. You can also potentially be able to name the item through re-exports. See below.
- If an item is private, it may be accessed by the current module and its descendants.
This means I cannot factor out horizontally and expect to limit horizontally at the same time. Since the main.rs
is on the top level anything on the same level that is public is accessible to the whole crate as any other module since every module can access the parent of the top level.
Therefore the answer for refactoring to the same level into a file (module) is: No.
On a side note, if I had factored out the code into a file called lib.rs
then the only difference would have been the path, as lib.rs
on the top level is implicitly just the crate
path, while in a file called game.rs
the path would be crate::game
.
But the same behavior as the single file can be done by factoring out vertically. We create a directory called game
and move game.rs
inside this directory and add the pub keyword to MyGame: pub struct MyGame
.
Similar to the python __init__.py
file rust needs a file mod.rs
to make the directory a module.
Inside the mod.rs
you declare the files you have inside, mod game
in our case.
Now we could address MyGame by the path crate::game::game::MyGame
, however since game.rs
is declared private the access to MyGame is sealed, as all elements of the path have to be public.
However, since MyGame is declared public, any module on the same level has access to it.
We cannot move the main.rs
into the game directory but we can factor the code inside it into another function. Let's call it init
for lack of fantasy. We put the init function inside a file called init.rs
inside the game directory and declare it public inside mod.rs
like so: pub mod init
.
Now we can call game::init::init()
because it is public, but not game::game::MyGame
. Init however, has access to MyGame
.
The final structure looks like this:
src
|---main.rs
|---game
|---mod.rs
|---game.rs
|---init.rs
main.rs:
mod game;
use game::init;
fn main() {
init::init();
}
mod.rs:
pub mod init;
mod game;
game.rs:
use ggez::event::EventHandler;
use ggez::{graphics, Context, GameResult};
pub struct MyGame {
// Your state here...
}
impl MyGame {
pub fn new(_ctx: &mut Context) -> MyGame {
// Load/create resources such as images here.
MyGame {
// ...
}
}
}
impl EventHandler for MyGame {
fn update(&mut self, _ctx: &mut Context) -> GameResult<()> {
// Update code here...
Ok(())
}
fn draw(&mut self, ctx: &mut Context) -> GameResult<()> {
graphics::clear(ctx, graphics::WHITE);
// Draw code here...
graphics::present(ctx)
}
}
init.rs:
use crate::game::game::MyGame;
use ggez::{ContextBuilder, event};
pub fn init() {
// Make a Context.
let (mut ctx, mut event_loop) = ContextBuilder::new("my_game", "Cool Game Author")
.build()
.expect("aieee, could not create ggez context!");
// Create an instance of your event handler.
// Usually, you should provide it with the Context object to
// use when setting your game up.
let mut my_game = MyGame::new(&mut ctx);
// Run!
match event::run(&mut ctx, &mut event_loop, &mut my_game) {
Ok(_) => println!("Exited cleanly."),
Err(e) => println!("Error occured: {}", e),
}
}
Upvotes: 2