Fintech altyapısı, bir fiyatlandırma motoru, bir CLI aracı ya da yüksek verimli bir arka uç geliştiriyorsanız, er ya da geç canlı döviz verisine ihtiyaç duyacaksınız. Bir Rust'ta döviz kuru API'si bağlamak, dilin sunduğu en temiz ağ görevlerinden biridir: reqwest, tokio ve serde ile asenkron bir HTTP istemcisi, otomatik JSON serisizleştirme ve kur işleme kodunuzun daha çalışmadan doğru olduğuna dair derleme zamanı garantileri elde edersiniz. Bu rehber tüm yolu adım adım anlatır — ilk GET isteğinizden, yeniden kullanılabilir bir istemci, tipli modeller, özel hata işleme, ondalık açısından güvenli para matematiği ve sizi ücretsiz katmanda tutan bellek içi bir önbelleğe sahip üretime hazır bir döviz çeviriciye kadar.
Sonunda, Finexly API dokümantasyonu üzerine kurulu, 170'ten fazla para biriminin herhangi ikisi arasında gerçek zamanlı kurlarla dönüşüm yapan, küçük ve yeniden kullanılabilir bir Rust modülüne sahip olacaksınız. Go döviz API rehberimizi daha önce okuduysanız, bu onun aynı mimariye sahip Rust karşılığıdır — tipli bir istemci, bir önbellek ve temiz hata yayılımı.
Rust Neden Döviz Verisi İçin Harika Bir Seçim
Döviz verisi G/Ç'ye bağlıdır, gecikmeye duyarlıdır ve — parayla ilgilenen her sistemde — doğruluk açısından kritiktir. Rust bu üç noktanın hepsine olağandışı derecede uygundur:
- Varsayılan olarak asenkron.
reqwest,hypervetokioüzerine kuruludur, dolayısıyla eşzamanlı kur çekmeleri ucuzdur ve engellemez. İşletim sistemi iş parçacıkları oluşturmadan onlarca para birimi çiftini paralel olarak isteyebilirsiniz. - Tip sistemi hataları erken yakalar.
serdeile türetilen bir struct, bozuk bir yanıtın veya eksik bir alanın üretimde üç kat derinde gizemli birnullyerine derleme zamanı ya da serisizleştirme zamanı hatası olacağı anlamına gelir. - Çöp toplayıcı yok, öngörülebilir gecikme. Yük altında döviz kurları sunan bir servis için, çöp toplayıcı duraklamalarının olmaması önemlidir.
- Ondalık açısından güvenli para.
rust_decimalcrate'i ile sabit noktalı aritmetik elde edersiniz, böylece €100.00'ı €99.999999'a çeviren bir yuvarlama hatasını asla yayınlamazsınız.
Ödün, bir betik dilinden biraz daha dik bir kurulumdur, ancak sonuç bir servise yerleştirip güvenebileceğiniz bir döviz istemcisidir.
Ne İnşa Edeceksiniz
Şunları yapan, yeniden kullanılabilir bir FinexlyClient:
- Bir döviz kuru API'sinden bir taban para birimi için en güncel kurları çeker.
serdeile JSON'u tipli Rust struct'larına serisizleştirir.- Hiçbiri taban olmasa bile (çapraz kurlar) herhangi iki para birimi arasında dönüşüm yapar.
- Hataları — ağ arızaları, 200 olmayan yanıtlar, hız sınırları — özel bir hata türüyle işler.
- Yanıtları bir TTL ile bellekte önbelleğe alır, böylece rahatça ücretsiz planın içinde kalırsınız.
Ön Koşullar ve Proje Kurulumu
Güncel ve kararlı bir Rust araç zincirine (rustup ile kurun) ve ücretsiz bir Finexly API anahtarına ihtiyacınız var. Bir dakikadan kısa sürede ücretsiz kayıt olabilirsiniz — kredi kartı gerekmez, ücretsiz katmanda ayda 1.000 istek.
Yeni bir proje oluşturun ve bağımlılıkları ekleyin:
cargo new finexly-rates
cd finexly-rates
cargo add tokio --features full
cargo add reqwest --features json
cargo add serde --features derive
cargo add thiserror
cargo add rust_decimal rust_decimal_macrosCargo.toml bağımlılıklarınız artık şöyle görünmelidir:
[dependencies]
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1", features = ["derive"] }
thiserror = "2"
rust_decimal = "1"
rust_decimal_macros = "1"Crate'ler hakkında kısa bir not: reqwest, Rust için fiilî asenkron HTTP istemcisidir; json özelliği serde_json'u getirir ve .json() yardımcısını etkinleştirir. derive ile serde size #[derive(Deserialize)] sağlar. thiserror, kullanışlı özel hata enum'ları oluşturmayı kolaylaştırır. rust_decimal, para için sabit noktalı ondalıklar sağlar.
Anahtarınızı bir ortam değişkeninde saklayın, böylece asla sürüm kontrolüne düşmez:
export FINEXLY_API_KEY="your_api_key_here"Finexly API Yanıt Yapısı
Herhangi bir kod yazmadan önce, uç noktanın ne döndürdüğüne bakın. En güncel kurlar uç noktasına yapılan bir istek:
curl "https://api.finexly.com/v1/latest?base=USD&symbols=EUR,GBP,JPY&apikey=YOUR_KEY"küçük, öngörülebilir bir JSON nesnesi döndürür:
{
"success": true,
"base": "USD",
"timestamp": 1753660800,
"rates": {
"EUR": 0.9213,
"GBP": 0.7847,
"JPY": 161.42
}
}İki şey aşağıdaki kodu şekillendirir. Birincisi, rates düz bir eşlemedir — ISO 4217 para birimi kodundan bir kayan noktalı sayıya — ve bu, Rust'taki HashMap<String, f64> türüne temiz bir şekilde oturur. İkincisi, base bu kurların neye göre olduğunu söyler. USD → EUR dönüşümü için rates["EUR"] ile çarparsınız. Taban olmayan iki para birimi arasında dönüşüm için taban üzerinden geçersiniz: birine böler, diğeriyle çarparsınız.
reqwest ile İlk İsteğinizi Yapmak
Mümkün olan en basit asenkron istekle başlayın. src/main.rs dosyasını şununla değiştirin:
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let api_key = std::env::var("FINEXLY_API_KEY")
.expect("Set FINEXLY_API_KEY in your environment");
let url = format!(
"https://api.finexly.com/v1/latest?base=USD&symbols=EUR,GBP,JPY&apikey={api_key}"
);
let body = reqwest::get(&url).await?.text().await?;
println!("{body}");
Ok(())
}cargo run ile çalıştırın ve ham JSON'un yazdırıldığını göreceksiniz. #[tokio::main] makrosu asenkron çalışma zamanını kurar, reqwest::get isteği gerçekleştirir ve her .await? görevi ağ yanıt verene kadar askıya alırken oluşan hataları yayar. Bu çalışır, ancak reqwest::get her çağrıda yepyeni bir istemci oluşturur — tek seferlik bir kullanım için uygun, gerçek bir uygulama için yanlış. Bunu kısa süre içinde düzelteceğiz.
Serde ile JSON'u Modellemek
Bir dizgeyi yazdırmak yararlı değildir; tipli veri istersiniz. Yanıtı yansıtan struct'lar tanımlayın ve ayrıştırmayı serde'ye bırakın:
use serde::Deserialize;
use std::collections::HashMap;
#[derive(Debug, Deserialize)]
pub struct RatesResponse {
pub success: bool,
pub base: String,
pub timestamp: i64,
pub rates: HashMap<String, f64>,
}Şimdi .text() yerine, gövdeyi doğrudan struct'ınıza serisizleştiren .json()'u koyun:
let data: RatesResponse = reqwest::get(&url).await?.json().await?;
println!("1 USD = {} EUR", data.rates["EUR"]);json() yöntemi yanıt gövdesini okur ve arka planda serde_json'u çalıştırır. Bir alan eksikse veya yanlış türdeyse, sessiz bir null yerine anlaşılır bir serisizleştirme hatası alırsınız. rates bir HashMap olduğundan, döndürülen herhangi bir para birimini ISO 4217 koduyla arayabilirsiniz.
Yeniden Kullanılabilir Bir İstemci İnşa Etmek
En yaygın reqwest hatası, her istek için yeni bir Client oluşturmaktır. Her istemci kendi bağlantı havuzuna sahiptir, bu yüzden onu yeniden oluşturmak keep-alive bağlantılarını ve TLS el sıkışmalarını çöpe atar. Bir tane Client oluşturun, onu ucuza klonlayın (içeride bir Arc'tır) ve her yerde yeniden kullanın. Onu URL'yi ve anahtarı gizleyen küçük bir struct içine sarın:
use reqwest::Client;
#[derive(Clone)]
pub struct FinexlyClient {
http: Client,
api_key: String,
base_url: String,
}
impl FinexlyClient {
pub fn new(api_key: impl Into<String>) -> Self {
Self {
http: Client::builder()
.timeout(std::time::Duration::from_secs(10))
.build()
.expect("failed to build HTTP client"),
api_key: api_key.into(),
base_url: "https://api.finexly.com/v1".to_string(),
}
}
pub async fn latest(
&self,
base: &str,
symbols: &[&str],
) -> Result<RatesResponse, FinexlyError> {
let url = format!("{}/latest", self.base_url);
let symbols_csv = symbols.join(",");
let resp = self
.http
.get(&url)
.query(&[
("base", base),
("symbols", symbols_csv.as_str()),
("apikey", self.api_key.as_str()),
])
.send()
.await?;
let resp = resp.error_for_status()?;
let data = resp.json::<RatesResponse>().await?;
Ok(data)
}
}Vurgulamaya değer birkaç nokta var. .query(&[...]) oluşturucusu URL kodlamasını sizin için halleder, böylece sorgu dizelerini asla elle birleştirmezsiniz. Oluşturucudaki .timeout(...), takılı kalan bir bağlantının servisinizi durdurmasına karşı koruma sağlar. Ve error_for_status(), 2xx olmayan herhangi bir HTTP yanıtını bir Err'e dönüştürür — bu da bizi hata işlemeye getirir.
Sağlam Hata İşleme
Üretim kodunun şu soruyu yanıtlaması gerekir: ağ çöktüğünde, anahtar geçersiz olduğunda (403) veya hız sınırına ulaştığınızda (429) ne olur? Bu durumları thiserror kullanarak özel bir hata enum'uyla modelleyin:
use thiserror::Error;
#[derive(Debug, Error)]
pub enum FinexlyError {
#[error("HTTP request failed: {0}")]
Http(#[from] reqwest::Error),
#[error("API returned an unsuccessful response")]
Unsuccessful,
#[error("currency '{0}' was not present in the response")]
MissingCurrency(String),
}#[from] reqwest::Error satırı, herhangi bir reqwest arızasının — DNS, TLS, zaman aşımı veya error_for_status() tarafından yakalanan 2xx olmayan bir durum — ? operatörü aracılığıyla otomatik olarak FinexlyError::Http'ye dönüştürüldüğü anlamına gelir. latest() içindeki her .await?'nin sorunsuz çalışmasının nedeni budur.
Özel bir davranış istediğinizde durum kodunu açıkça da inceleyebilirsiniz — örneğin, bir 429'da geri çekilmek için:
if resp.status() == reqwest::StatusCode::TOO_MANY_REQUESTS {
// sleep and retry, or fall back to a cached rate
}Yeniden denemeler, geri çekilme ve önbellekleme stratejisinin daha ayrıntılı bir ele alınışı için döviz API önbellekleme ve hata işleme en iyi uygulamaları rehberimize bakın.
Herhangi İki Para Birimi Arasında Dönüşüm
Tek bir kurla çarpmak yalnızca kaynak para biriminiz taban olduğunda işe yarar. Gerçek uygulamalar, hiçbir tarafın taban olmadığı keyfi çiftlere — EUR → JPY, GBP → CAD — ihtiyaç duyar. Püf noktası, taban para birimi üzerinden yönlendirmektir: taban karşılığını elde etmek için tutarı kaynak kura bölün, ardından hedef kurla çarpın.
impl FinexlyClient {
/// Convert `amount` from `from` to `to`, using `from` as the request base.
pub async fn convert(
&self,
amount: f64,
from: &str,
to: &str,
) -> Result<f64, FinexlyError> {
let data = self.latest(from, &[to]).await?;
if !data.success {
return Err(FinexlyError::Unsuccessful);
}
let rate = data
.rates
.get(to)
.ok_or_else(|| FinexlyError::MissingCurrency(to.to_string()))?;
Ok(amount * rate)
}
}Burada kurları basitçe from'u taban alarak isteriz, dolayısıyla döndürülen kur zaten doğrudan from → to dönüşümüdür. Tek bir taban para birimini (diyelim USD) önbelleğe alıyorsanız ve çapraz kurlara ihtiyacınız varsa, bunları bunun yerine istemci tarafında hesaplayın: amount / rates[from] * rates[to]. Baştan sona deneyin:
#[tokio::main]
async fn main() -> Result<(), FinexlyError> {
let client = FinexlyClient::new(
std::env::var("FINEXLY_API_KEY").expect("set FINEXLY_API_KEY"),
);
let usd = client.convert(250.0, "EUR", "USD").await?;
println!("250 EUR = {usd:.2} USD");
Ok(())
}Para İçin rust_decimal Kullanmak
f64, görüntüleme amaçlı dönüşümler için uygundur, ancak bakiyeleri saklamak, fatura kesmek veya kuruşuna kadar mutabık kalması gereken herhangi bir şey için yanlış türdür. İkili kayan nokta 0.1'i tam olarak temsil edemez ve bu hatalar birikir. Para için sabit noktalı ondalıklar kullanın:
use rust_decimal::Decimal;
use rust_decimal::prelude::FromPrimitive;
use rust_decimal_macros::dec;
pub fn convert_decimal(amount: Decimal, rate: f64) -> Decimal {
let rate = Decimal::from_f64(rate).unwrap_or_default();
(amount * rate).round_dp(2)
}
// usage
let total = convert_decimal(dec!(1999.99), 0.9213);
println!("{total}"); // rounded to 2 decimal placesÖnemli bir uyarı: her para biriminin iki alt birimi yoktur. JPY ve KRW'nin sıfır ondalık basamağı vardır; BHD ve KWD gibi bazı para birimlerinin üç basamağı vardır. 2'yi sabit kodlamak yerine, her para birimi için doğru sayıda ondalığa (ISO 4217 alt birim tablosuna göre) yuvarlayın. Hem döviz çeviricimiz hem de API dokümantasyonu bu para birimi bazlı kuralları yansıtır.
Ücretsiz Katmanın İçinde Kalmak İçin Önbellekleme
Döviz kurları saniyeden saniyeye kayda değer şekilde hareket etmez. Çoğu görüntüleme, ödeme ve raporlama kullanım durumu için saatte bir kez yenilemek fazlasıyla yeterlidir — ve önbellekleme, ne kadar trafik alırsanız alın sizi rahatça ücretsiz planın içinde tutar. Bir Mutex ile korunan basit bir bellek içi TTL önbelleği ekleyin:
use std::collections::HashMap;
use std::sync::Mutex;
use std::time::{Duration, Instant};
struct CacheEntry {
data: RatesResponse,
fetched_at: Instant,
}
pub struct CachedClient {
inner: FinexlyClient,
ttl: Duration,
cache: Mutex<HashMap<String, CacheEntry>>,
}
impl CachedClient {
pub fn new(api_key: impl Into<String>) -> Self {
Self {
inner: FinexlyClient::new(api_key),
ttl: Duration::from_secs(3600), // 1 hour
cache: Mutex::new(HashMap::new()),
}
}
pub async fn latest(&self, base: &str) -> Result<RatesResponse, FinexlyError> {
{
let cache = self.cache.lock().unwrap();
if let Some(entry) = cache.get(base) {
if entry.fetched_at.elapsed() < self.ttl {
return Ok(entry.data.clone());
}
}
}
let fresh = self.inner.latest(base, &[]).await?;
let mut cache = self.cache.lock().unwrap();
cache.insert(
base.to_string(),
CacheEntry { data: fresh.clone(), fetched_at: Instant::now() },
);
Ok(fresh)
}
}Bunun derlenmesi için RatesResponse üzerinde #[derive(Clone)] isteyeceksiniz. Bir saatlik TTL ile, kullanıcılarınız kaç dönüşüm yaparsa yapsın tek bir taban para birimi size günde en fazla 24 isteğe mal olur — 1.000 istekli ücretsiz katmana karşı bir yuvarlama hatası. Bunu aşarsanız, fiyatlandırma planları doğrusal olarak ölçeklenir. stale-while-revalidate ve örnekler arasında paylaşılan önbellekler gibi desenler için önbellekleme ve hata işleme rehberimize bakın.
Geçmiş Kurları Çekmek
Geriye dönük tarihli faturalar, raporlama ve grafikler geçmiş veriye ihtiyaç duyar. Finexly, aynı istemciden /v1/historical'a giden bir yöntem ekleyerek çağırabileceğiniz bir geçmiş uç noktası sunar:
impl FinexlyClient {
pub async fn historical(
&self,
date: &str, // "YYYY-MM-DD"
base: &str,
symbols: &[&str],
) -> Result<RatesResponse, FinexlyError> {
let url = format!("{}/historical", self.base_url);
let symbols_csv = symbols.join(",");
let data = self
.http
.get(&url)
.query(&[
("date", date),
("base", base),
("symbols", symbols_csv.as_str()),
("apikey", self.api_key.as_str()),
])
.send()
.await?
.error_for_status()?
.json::<RatesResponse>()
.await?;
Ok(data)
}
}Yanıt yapısı en güncel kurlar uç noktasıyla aynıdır, dolayısıyla mevcut ayrıştırma ve dönüşüm kodunuzun tamamı sorunsuz çalışır. Tam parametre listesi ve tarih aralıkları için /v1/timeseries uç noktası için API dokümantasyonuna bakın.
Kaçınılması Gereken Yaygın Tuzaklar
- Her istek için bir
Clientoluşturmak. Bir tane oluşturun, onu klonlayın (ucuzdur) ve yeniden kullanın, böylece bağlantı havuzunu sıcak tutarsınız. - Saklanan bakiyeler için
f64kullanmak. Görüntüleme için uygun, defterler için yanlış — mutabık kalması gereken her şey içinrust_decimalkullanın. - İki ondalık basamağı sabit kodlamak. JPY ve KRW'de sıfır vardır; birkaç para biriminde üç vardır. ISO 4217 alt birimlerini kullanarak her para birimi için yuvarlayın.
- Durum kontrolünü atlamak. Bir
403veya429gövdesini kur verisi olarak serisizleştirmek kafa karıştırıcı hatalar üretir.error_for_status()bunu tek satırda halleder. - Her dönüşümde çekmek. Bir TTL ile önbelleğe alın ve kotanızı yakmamak için kullanıcı girdisine debounce uygulayın.
- API anahtarınızı commit'lemek. Onu ortamdan veya bir sır yöneticisinden okuyun, asla
main.rsiçinde bir dizge sabiti olarak değil.
Hâlâ sağlayıcıları karşılaştırıyorsanız, döviz API'leri karşılaştırmamız doğruluğu, para birimi kapsamını ve ücretsiz katman sınırlarını ayrıntılandırır, ve ücretsiz döviz API rehberi sıfır maliyetli seçenekleri daha derinlemesine ele alır.
Sıkça Sorulan Sorular
Rust'ta bir döviz API'sini çağırmak için en iyi HTTP istemcisi hangisidir?
reqwest, asenkron Rust için standart tercihtir. hyper ve tokio üzerine kuruludur, bağlantı havuzlamayı, TLS'yi ve JSON'u kutudan çıkar çıkmaz halleder ve tipli yanıtlar için serde ile doğal olarak eşleşir. json özelliğini etkinleştirin ve uygulamanız boyunca tek bir Client örneğini yeniden kullanın.Rust'ta bir JSON döviz kuru yanıtını nasıl ayrıştırırım?
Yanıtı yansıtan bir struct tanımlayın ve üzerindeserde::Deserialize türetin. rates alanını, para birimi kodundan kura düz bir eşleme olduğu için HashMap<String, f64> olarak modelleyin, ardından yanıt üzerinde .json::<RatesResponse>() çağırın; reqwest onu sizin için serisizleştirir.Rust'ta döviz dönüşümü için f64 mi Decimal mı kullanmalıyım?
Hızlı görüntüleme dönüşümleri içinf64 kullanın, ancak sakladığınız veya mutabakat yaptığınız her şey — bakiyeler, faturalar, defterler — için rust_decimal::Decimal kullanın. İkili kayan nokta ondalık kesirleri tam olarak temsil edemez, bu yüzden yuvarlama hataları birikir. rust_decimal size sabit noktalı hassasiyet sağlar.Bir Rust servisinde döviz kurlarını ne sıklıkla çekmeliyim?
Çoğu ödeme, görüntüleme ve raporlama kullanım durumu için saatte bir kez fazlasıyla yeterlidir — kurlar bir saat içinde önemli olacak kadar hareket etmez. Yanıtı bir saatlik TTL ile önbelleğe alın; bu sizi taban para birimi başına günde kabaca 24 çağrıda tutar ve rahatça ücretsiz planın içinde kalırsınız.Rust'ta geçmiş döviz kurları alabilir miyim?
Evet. Aynı istemcide/v1/historical?date=YYYY-MM-DD uç noktasını çağıran bir yöntem ekleyin. JSON yapısı en güncel kurlar uç noktasıyla eşleşir, dolayısıyla mevcut struct'larınız ve dönüşüm mantığınız değişmeden çalışır. Bu, geriye dönük tarihli faturalar, raporlama ve grafikler için yararlıdır.İnşaya Başlayın
Gerçek zamanlı döviz kurlarını Rust projenize entegre etmeye hazır mısınız? Ücretsiz Finexly API anahtarınızı alın — kredi kartı gerekmez. Ayda 1.000 ücretsiz istekle başlayın ve büyüdükçe yükseltin. Tam uç nokta referansı için API dokümantasyonuna bakın ve çok para birimli özelliklerinizi ölçeklendirmeye hazır olduğunuzda fiyatlandırma planlarını inceleyin.
Explore More
Vlado Grigirov
Senior Currency Markets Analyst & Financial Strategist
Vlado Grigirov is a senior currency markets analyst and financial strategist with over 14 years of experience in foreign exchange markets, cross-border finance, and currency risk management. He has wo...
View full profile →