DEV Community

Digkill
Digkill

Posted on

Experience developing in Rust

Image description

I am the CEO of my company MediaRise (the company is just starting to develop), with a technical background. I enjoy writing in different languages, or rather trying to work with them. I have heard about a language called Rust for a long time.

Key Features of Rust

Memory Safety: Rust guarantees memory safety by using a system of ownership with rules that the compiler checks at compile time. This prevents common bugs such as null pointer dereferencing and buffer overflows.

Concurrency: Rust makes it easier to write safe concurrent code. The ownership system ensures that data races are avoided, making concurrent programming safer and more efficient.

Performance: Rust is designed for performance. Its zero-cost abstractions mean that you can write high-level code without compromising on performance. Rust code can be as fast as C or C++.

Safe Systems Programming: Rust allows low-level programming without the fear of undefined behavior. It offers control over hardware and memory resources while maintaining safety guarantees.

Modern Language Features: Rust includes modern language features such as pattern matching, type inference, and a powerful macro system. These features help in writing clean and maintainable code.

Cargo and Crates: Rust's package manager, Cargo, and the crates.io ecosystem make dependency management and building projects straightforward and efficient.

Strong Type System: Rust has a strong and static type system that prevents many programming errors at compile time. It includes advanced features such as generics, trait bounds, and lifetimes.

Community and Documentation: Rust has a vibrant and supportive community, with comprehensive documentation and a wealth of learning resources.

These features make Rust a compelling choice for developers looking to build reliable and efficient software.

I was thinking about what to implement in Rust. I wanted to write a Telegram bot that would translate messages in a group chat bidirectionally.

I understand that there are ready-made solutions, but I wanted to create my own "bicycle" and try out a new language for myself — Rust.

I have the idea, and I have no problems creating a Telegram bot, but I encountered an issue with the service that would translate from one language to another. I chose LibreTranslate. To use it for free, you need to deploy the application on your server. So I did: I installed the dependencies and deployed it in a Docker container. There were no issues, except that the application needs to be run with the flag: ./run.sh --api-keys to obtain an api_key. You can read more about setting up and obtaining the api-key for LibreTranslate on the official website.

After deploying the LibreTranslate server and getting the token for the Telegram bot, the next step was to implement the application. After reviewing the official Rust documentation and some GitHub projects, I started creating. I installed Rust and Cargo (the package registry/manager for Rust). After studying the documentation for the teloxide library for creating Telegram bots in Rust, I wrote a handler for message events in the chat.

#[tokio::main]
async fn main() -> ResponseResult<()> {
    dotenv().ok();
    env_logger::init();
    let token_bot = env::var("TELEGRAM_BOT_KEY").expect("TELEGRAM_BOT_KEY not found");

    let bot = teloxide::Bot::new(&token_bot).parse_mode(ParseMode::Html);

    // Create a handler for our bot, that will process updates from Telegram
    let handler = dptree::entry()
        .inspect(|u: Update| {
                    eprintln!("{u:#?}"); // Print the update to the console with inspect
        })

   ...

        .branch(
            Update::filter_message()
                .branch(
                    dptree::endpoint(translate_message),
                )
        );

    // Create a dispatcher for our bot
    Dispatcher::builder(bot, handler).enable_ctrlc_handler().build().dispatch().await;

    Ok(())
}

async fn translate_message(bot: Bot, msg: Message) -> ResponseResult<()> {
    if let Some(text) = msg.text() {

        match translate(text).await {
            Ok(translated_word) => {
                bot.send_message(msg.chat.id, translated_word).await?;
            }
            Err(e) => {
                bot.send_message(msg.chat.id, format!("Translation error: {}", e)).await?;
            }
        }
    } else {
        bot.send_message(msg.chat.id, "Send me plain text.").await?;
    }

    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

Next is the function itself for requesting a translation from the LiberTranslate API. The translation application determines what language the message is in and translates it into the required one

async fn translate(text: &str) -> ResponseResult<String> {
    let client = reqwest::Client::new();

    let api_translate_url = env::var("API_TRANSLATE_URL").expect("API_TRANSLATE_URL not found");
    let api_translate_key = env::var("API_TRANSLATE_KEY").expect("API_TRANSLATE_KEY not found");
    let api_detect_url = env::var("API_DETECT_URL").expect("API_DETECT_URL not found");
    eprintln!("{text:#?}");

    let detect_request = DetectRequest {
        q: String::from(text),
        api_key: String::from(api_translate_key.clone()),
    };

    let res = client.post(api_detect_url)
        .header("Content-Type", "application/x-www-form-urlencoded")
        .form(&detect_request)
        .send()
        .await?;
    let resp_json = res.json::<Vec<DetectResponse>>().await?;

    let lang = &resp_json[0].language;

    eprintln!("{lang:#?}");
    let target_lang = if lang == "ru" { "th" } else { "ru" };

    let json_object = json!({
                "q": text,
                "source": "auto",
                "target": target_lang,
                "format": "text",
                "alternatives": 3,
                "api_key": api_translate_key
            });

    let json_string = serde_json::to_string(&json_object).unwrap();


    let res = client.post(api_translate_url)
        .body(json_string)
        .header("Content-Type", "application/json")
        .send()
        .await?;

    let resp_json = res.json::<TranslateResult>().await?;
    let translated_word = resp_json.translatedText;
    Ok(translated_word)
}
Enter fullscreen mode Exit fullscreen mode

Result in Telegram chat:
->Hi! How are you?
<-เฮ้! เป็นไงบ้าง?

Image description

There were problems with bugs, which were due to the fact that I was using old Rust libraries when I tried to solve the problem using ChatGPT. But the live experience of developers on GitHub helped more, where I watched the implementation of some functionality: API documentation of the bot, working with env (environment files), examples of working with json, sending rest requests, and also the work of the language design.

I'm very excited about the working application because I haven't written in Rust before and it inspired me to study it further. Experienced developers will probably tell you how to optimize the code, which I will be very happy about.

All that remains is to build the application and run it in daemon mode on the server

  1. Build your application using the command:
    cargo build --release

  2. Create a unit file for systemd. For example, let's create a file
    /etc/systemd/system/translate_bot.service

[Unit]
Description=Translate Bot
After=network.target

[Service]
ExecStart=/path/to/your/application
Restart=always
User=yourusername
Group=yourgroupname
Environment=/path/to/your/.env
WorkingDirectory=/path/to/your/application/directory

[Install]
WantedBy=multi-user.target
Enter fullscreen mode Exit fullscreen mode
  1. Reload your systemd configuration to let it know about the new service:
    sudo systemctl daemon-reload

  2. Start the service:
    sudo systemctl start translate_bot

  3. Make sure the service is up and running:
    sudo systemctl status translate_bot

7.To have your service automatically start when the system starts, run the command:
sudo systemctl enable rust_service

Your Rust application will run as a system service managed by systemd. You can control it using standard systemd commands such as start, stop, restart, and status.

P.S. I would also like to solve the problem with env files so that they can be accessed in a function from main.

Next in my plans: I would like to implement event processing if a new user appears in the chat (and also showers). The user can choose the language in which he would like to receive translations. The users themselves and their settings will be stored in the PostgreSQL database

Thank you all for your attention! I posted the source code on GitHub.

Source on GitHub

Top comments (0)