Warning This is an early experimental version. The API is unstable and may change significantly. Do NOT use in production environments.
A high-performance RPC framework for Rust, compatible with the TARS ecosystem.
TarsRust is the Rust implementation of the TARS RPC framework, providing the same functionality as TarsCpp and other TARS language implementations. It enables Rust applications to seamlessly integrate with TARS microservices infrastructure.
- High Performance: Built on Tokio async runtime for excellent concurrency
- Protocol Compatible: Full compatibility with TARS protocol (v1/v2/v3)
- Load Balancing: Multiple strategies including Round Robin, Random, Mod Hash, and Consistent Hash
- Service Discovery: Integration with TARS registry for automatic service discovery
- Transport Layer: Support for TCP, UDP, and TLS/SSL
- Fault Tolerance: Built-in health checking, connection pooling, and automatic reconnection
- Observability: Structured logging with tracing, statistics reporting
Add the following to your Cargo.toml:
[dependencies]
tars = { git = "https://github.com/TarsCloud/TarsRust" }
tokio = { version = "1.35", features = ["full"] }use tars::{Communicator, Result};
#[tokio::main]
async fn main() -> Result<()> {
// Create a communicator
let comm = Communicator::new();
// Create a servant proxy with direct endpoint
let proxy = comm.string_to_proxy(
"Hello.HelloServer.HelloObj@tcp -h 127.0.0.1 -p 18015"
)?;
// Set timeout (milliseconds)
proxy.set_timeout(3000);
// Make RPC call (you need to encode request according to your .tars interface)
let request_data = encode_request("sayHello", "World");
let response = proxy.invoke("sayHello", &request_data).await?;
println!("Response: {:?}", response);
Ok(())
}Define your interface in a .tars file:
// Hello.tars
module Hello {
interface HelloWorld {
int sayHello(string name, out string greeting);
};
};
Implement the service:
use tars::{Application, Result};
// Implement the HelloWorld interface
struct HelloWorldImp;
impl HelloWorldImp {
// sayHello returns a greeting message
fn say_hello(&self, name: &str) -> (i32, String) {
let greeting = format!("Hello, {}! Welcome to Tars.", name);
(0, greeting)
}
}
#[tokio::main]
async fn main() -> Result<()> {
// Get server config
let cfg = Application::get_server_config();
println!("Starting server: {}.{}", cfg.app, cfg.server);
// Create service implementation
let imp = HelloWorldImp;
// Register servant and start server
let app = Application::new();
app.add_servant("Hello.HelloServer.HelloWorldObj", imp)?;
println!("Server is running...");
app.run().await
}TarsRust is organized into the following layers:
┌─────────────────────────────────────────────────────────────┐
│ Application Layer │
│ (Application lifecycle, configuration) │
├─────────────────────────────────────────────────────────────┤
│ Proxy Layer │
│ (ServantProxy, Communicator, Filters) │
├─────────────────────────────────────────────────────────────┤
│ Endpoint Layer │
│ (Endpoint management, Load balancing, Health check) │
├─────────────────────────────────────────────────────────────┤
│ Transport Layer │
│ (TCP/UDP/TLS connection management) │
├─────────────────────────────────────────────────────────────┤
│ Protocol Layer │
│ (TARS protocol encoding/decoding) │
└─────────────────────────────────────────────────────────────┘
| Module | Description |
|---|---|
codec |
TLV encoding/decoding for TARS protocol |
protocol |
Request/Response packet structures |
endpoint |
Endpoint definition and management |
selector |
Load balancing strategies |
transport |
Client and server transport implementations |
registry |
Service discovery and registration |
communicator |
Client-side communication management |
application |
Application lifecycle management |
filter |
Client and server filter middleware |
logger |
Remote logging support |
stat |
Statistics reporting |
TarsRust supports multiple load balancing strategies:
| Strategy | Description | Use Case |
|---|---|---|
| Round Robin | Cycles through endpoints in order | Default, general purpose |
| Random | Randomly selects an endpoint | Even distribution |
| Mod Hash | Selects based on hash % node_count | Session affinity |
| Consistent Hash | Virtual nodes with consistent hashing | Minimizes redistribution on changes |
use tars::selector::{HashType, DefaultMessage};
// Create a message with hash routing
let msg = DefaultMessage::with_hash(user_id.hash(), HashType::ConsistentHash);
let endpoint = selector.select(&msg)?;use tars::transport::TarsClientConfig;
use std::time::Duration;
let config = TarsClientConfig::tcp()
.with_queue_len(10000)
.with_idle_timeout(Duration::from_secs(600))
.with_read_timeout(Duration::from_secs(30))
.with_write_timeout(Duration::from_secs(30))
.with_dial_timeout(Duration::from_secs(3));use tars::transport::TarsServerConfig;
use std::time::Duration;
let config = TarsServerConfig::tcp("0.0.0.0:18015")
.with_max_invoke(200000)
.with_read_timeout(Duration::from_secs(30))
.with_write_timeout(Duration::from_secs(30))
.with_handle_timeout(Duration::from_secs(60))
.with_idle_timeout(Duration::from_secs(600));TarsRust can communicate with any TARS server. Here's an example of calling a TARS server:
module Hello {
interface HelloWorld {
int sayHello(string name, out string greeting);
};
};
use tars::{Application, Result};
// Implement the HelloWorld interface
struct HelloWorldImp;
impl HelloWorldImp {
fn say_hello(&self, name: &str) -> (i32, String) {
let greeting = format!("Hello, {}!", name);
(0, greeting)
}
}
#[tokio::main]
async fn main() -> Result<()> {
let cfg = Application::get_server_config();
let imp = HelloWorldImp;
let app = Application::new();
app.add_servant("Hello.HelloServer.HelloWorldObj", imp)?;
println!("Server running on {}:{}", cfg.host, cfg.port);
app.run().await
}use tars::{Communicator, Result};
#[tokio::main]
async fn main() -> Result<()> {
// Create communicator
let comm = Communicator::new();
// Create proxy to the server
let proxy = comm.string_to_proxy(
"Hello.HelloServer.HelloWorldObj@tcp -h 127.0.0.1 -p 18015"
)?;
// Call sayHello method
let name = "Rust Client";
let (ret, greeting) = proxy.say_hello(name).await?;
println!("Return: {}", ret);
println!("Greeting: {}", greeting);
Ok(())
}TarsRust provides comprehensive error types:
use tars::{TarsError, Result};
fn handle_error(result: Result<()>) {
match result {
Ok(_) => println!("Success"),
Err(TarsError::Timeout(ms)) => println!("Request timed out after {}ms", ms),
Err(TarsError::NoEndpoint) => println!("No available endpoint"),
Err(TarsError::ServiceNotFound(name)) => println!("Service not found: {}", name),
Err(TarsError::ServerError { code, message }) => {
println!("Server error: code={}, message={}", code, message)
}
Err(e) => println!("Other error: {}", e),
}
}| Protocol | Description | Use Case |
|---|---|---|
| TCP | Reliable, ordered delivery | Default, most use cases |
| UDP | Fast, connectionless | Low latency, can tolerate loss |
| SSL/TLS | Encrypted TCP | Security-sensitive communications |
use tars::Endpoint;
// TCP endpoint
let tcp_ep = Endpoint::tcp("127.0.0.1", 10000);
// UDP endpoint
let udp_ep = Endpoint::udp("127.0.0.1", 10001);
// SSL endpoint
let ssl_ep = Endpoint::ssl("127.0.0.1", 10002);
// Parse from string
let ep = Endpoint::from_string("tcp -h 127.0.0.1 -p 10000 -t 3000");Important protocol constants:
use tars::consts;
// Protocol versions
const TARS_VERSION: i16 = consts::TARS_VERSION; // 1
const TUP_VERSION: i16 = consts::TUP_VERSION; // 2
const JSON_VERSION: i16 = consts::JSON_VERSION; // 3
// Return codes
const SUCCESS: i32 = consts::TARS_SERVER_SUCCESS; // 0
const DECODE_ERR: i32 = consts::TARS_SERVER_DECODE_ERR; // -1
const QUEUE_TIMEOUT: i32 = consts::TARS_SERVER_QUEUE_TIMEOUT; // -2
const INVOKE_TIMEOUT: i32 = consts::TARS_INVOKE_TIMEOUT; // -3
// Default timeouts (ms)
const ASYNC_TIMEOUT: u64 = consts::DEFAULT_ASYNC_TIMEOUT; // 3000
const CONNECT_TIMEOUT: u64 = consts::DEFAULT_CONNECT_TIMEOUT; // 3000- Start the TarsRust HelloWorld server:
cargo run --example server- Run the TarsRust client:
cargo run --example client=== Rust Tars Client Test ===
Connecting to 127.0.0.1:18015...
Connected!
Calling Hello.HelloServer.HelloWorldObj.sayHello("Rust Client")...
Sending 95 bytes request...
Received 89 bytes response
Response packet:
Request ID: 1
Return Code: 0
Result Desc:
Buffer Length: 32
Function result:
Return: 0
Greeting: "Hello, Rust Client!"
=== TEST PASSED ===
Contributions are welcome! Please feel free to submit issues and pull requests.
This project is licensed under the BSD-3-Clause License - see the LICENSE file for details.