File Management in Tauri 2.0 with Rust
I recently posted on Reddit that “Tauri 2.0 Is a Nightmare to Learn” using my alternative account and found a lot of resonance with the community. Tauri 2.01 (v2) is a big step forward for the framework and a big step forward for Rust as well. But if you are just getting started with it, you might find that the documentation is not quite as thorough as you would like it to be, and the safety features are overly complicated, and none of the current AI’s have learned yet how to use v2 making them useless if not a hindrance!
When you are just getting started, opening, modifying, and saving files, for example,can seem quite a bit tricky. If you choose to use Rust rather than using the API provided by Tauri and struggle to get it working, this post is for you. Let’s make a simple text file editor (which I lovingly called fedit short for “fucking edit!” in my angry desperation to get this working) like this:
Setup
Clone the repository and navigate into the project folder:
git clone https://github.com/QuentinWach/fedit
cd fedit
Then run the app with:
npm run tauri dev
You should see a window popping up just like in the image above. You can open a text file by clicking on the Open button and then selecting a file using the system dialog window that will pop up as well as save the file by clicking on the Save button which will open another dialog window asking you to specify the file name and directory. Modifications can be done in the text editor window.
User Interface & Components
The UI here is created with React components2 you’ll find in the components/
folder. The Editor component returns the following:
<div className="editor-container">
<div className="button-container">
<button onClick={handleOpen}>Open</button>
<button onClick={handleSave}>Save</button>
</div>
<textarea
className="editor-textarea"
value={content}
onChange={handleContentChange}
placeholder="Type your text here..."
/>
</div>
You can see, it really is just the two mentioned buttons and a textarea below. But when clicking the buttons, the handleOpen
and handleSave
functions are called. Let’s look at handleOpen
as an example to understand how this works:
const handleOpen = async () => {
try {
// Open a file selection dialog
const selected = await open({
multiple: false,
filters: [{
name: 'Text',
extensions: ['md', 'txt']
}]
});
if (selected) {
// Read the file content using our Rust command
const fileContent = await invoke('open_file', {
path: selected
});
setContent(fileContent);
console.log('File opened successfully!');
}
} catch (error) {
console.error('Error opening file:', error);
}
};
We use the open
function to open a file selection dialog window to search for text files. This function (together with the save
function) is provided by the api
package which we import at the beginning of the .jsx
-file with:
import { open, save } from '@tauri-apps/plugin-dialog';
Next, we feed the selected file path to the invoke
function which is used to call the open_file
Rust function. To be able to use the invoke
function, we need to import at the beginning of the file like this:
import { invoke } from "@tauri-apps/api/core";
Rust Functions
Using invoke
we can call Rust3 functions from our JavaScript code. To do so, we need to declare the function in our main.rs
file:
#[tauri::command]
fn open_file(path: String) -> Result<String, String> {
fs::read_to_string(PathBuf::from(path))
.map_err(|e| e.to_string())
}
The first line #[tauri::command]
is used to declare the function as a Tauri command. This is necessary for the invoke
function to be able to call it from JavaScript. The function then takes the path of the directory as a String
as an argument and returns a Result<String, String>
which is a common Rust pattern for returning a value or an error.
For this, we import the fs
module to be able to read the file content and the PathBuf
struct to be able to handle the path as a PathBuf
object.
use std::fs;
use std::path::PathBuf;
We then make sure that we can call the open_file
(and save_file
) function from JavaScript and initialize the dialog plugin by adding the following lines to the build
function in the main.rs
file:
.invoke_handler(tauri::generate_handler![save_file, open_file])
.plugin(tauri_plugin_dialog::init())
Configuration
Lastly, and importantly, we need to make sure that we list the capabilities we want to use in our tauri.conf.json
file:
"security": {
"csp": null,
"capabilities": []
}
We just need to add the empty capabilities
list to the security
section to get it to work. If you don’t include it, Tauri’s security model may restrict access to core APIs like file system operations. This is because the absence implies a configuration omission, and Tauri errs on the side of caution. This is meant to prevent malicious code from accessing your system through your application but also part of Tauri’s philosophy of being aware of what your application is doing specifically.
We also need to add
"withGlobalTauri": true
to actually enable global Tauri API access in the JavaScript code.
Conclusion
With that, you should be able to open, edit, and save text files in your Tauri application.
One might compare this setup to how easy it is creating and modifying files using Python and wonder why we bother with Rust and all the hassle. But of course Python does not have the same security, it is absurdly slow, it is quite difficult to create beautiful and complex GUIs with Python which also easily compile into cross-platform executables.
So I hope this helped and if you have any questions or suggestions, feel free to leave a comment below. 😊