Spaces:
Running
Running
| <html lang="ru"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>C# to Rust UDP Converter</title> | |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet" /> | |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| :root { | |
| --primary: #6366f1; | |
| --primary-dark: #4f46e5; | |
| --secondary: #f97316; | |
| --bg-dark: #0f172a; | |
| --bg-card: #1e293b; | |
| --bg-code: #0d1117; | |
| --text-primary: #f1f5f9; | |
| --text-secondary: #94a3b8; | |
| --border: #334155; | |
| --success: #10b981; | |
| --error: #ef4444; | |
| } | |
| body { | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | |
| background: linear-gradient(135deg, var(--bg-dark) 0%, #1a1f3a 100%); | |
| color: var(--text-primary); | |
| min-height: 100vh; | |
| line-height: 1.6; | |
| } | |
| header { | |
| background: rgba(30, 41, 59, 0.8); | |
| backdrop-filter: blur(10px); | |
| border-bottom: 1px solid var(--border); | |
| padding: 1.5rem 2rem; | |
| position: sticky; | |
| top: 0; | |
| z-index: 100; | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); | |
| } | |
| .header-content { | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| gap: 1rem; | |
| } | |
| .logo { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| font-size: 1.5rem; | |
| font-weight: 700; | |
| color: var(--text-primary); | |
| } | |
| .logo i { | |
| color: var(--primary); | |
| font-size: 2rem; | |
| } | |
| .nav-links { | |
| display: flex; | |
| gap: 2rem; | |
| align-items: center; | |
| } | |
| .nav-links a { | |
| color: var(--text-secondary); | |
| text-decoration: none; | |
| transition: color 0.3s; | |
| font-weight: 500; | |
| } | |
| .nav-links a:hover { | |
| color: var(--primary); | |
| } | |
| .brand-link { | |
| color: var(--primary) ; | |
| font-weight: 600; | |
| } | |
| main { | |
| max-width: 1400px; | |
| margin: 2rem auto; | |
| padding: 0 2rem; | |
| } | |
| .converter-section { | |
| background: var(--bg-card); | |
| border-radius: 1rem; | |
| padding: 2rem; | |
| margin-bottom: 2rem; | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); | |
| border: 1px solid var(--border); | |
| } | |
| .section-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 1.5rem; | |
| flex-wrap: wrap; | |
| gap: 1rem; | |
| } | |
| .section-title { | |
| font-size: 1.75rem; | |
| font-weight: 700; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| } | |
| .section-title i { | |
| color: var(--primary); | |
| } | |
| .code-container { | |
| display: grid; | |
| grid-template-columns: 1fr; | |
| gap: 2rem; | |
| } | |
| @media (min-width: 1024px) { | |
| .code-container { | |
| grid-template-columns: 1fr 1fr; | |
| } | |
| } | |
| .code-block { | |
| position: relative; | |
| background: var(--bg-code); | |
| border-radius: 0.75rem; | |
| overflow: hidden; | |
| border: 1px solid var(--border); | |
| } | |
| .code-header { | |
| background: rgba(30, 41, 59, 0.8); | |
| padding: 1rem 1.5rem; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .language-tag { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| font-weight: 600; | |
| color: var(--text-primary); | |
| } | |
| .language-tag i { | |
| font-size: 1.25rem; | |
| } | |
| .csharp-tag i { | |
| color: #9b4f96; | |
| } | |
| .rust-tag i { | |
| color: #ce422b; | |
| } | |
| .code-actions { | |
| display: flex; | |
| gap: 0.5rem; | |
| } | |
| .btn-icon { | |
| background: rgba(99, 102, 241, 0.1); | |
| border: 1px solid var(--primary); | |
| color: var(--primary); | |
| padding: 0.5rem 0.75rem; | |
| border-radius: 0.5rem; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| font-size: 0.875rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .btn-icon:hover { | |
| background: var(--primary); | |
| color: white; | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 6px -1px rgba(99, 102, 241, 0.3); | |
| } | |
| .code-content { | |
| max-height: 500px; | |
| overflow-y: auto; | |
| padding: 1.5rem; | |
| } | |
| .code-content::-webkit-scrollbar { | |
| width: 8px; | |
| height: 8px; | |
| } | |
| .code-content::-webkit-scrollbar-track { | |
| background: var(--bg-dark); | |
| } | |
| .code-content::-webkit-scrollbar-thumb { | |
| background: var(--primary); | |
| border-radius: 4px; | |
| } | |
| pre { | |
| margin: 0; | |
| font-family: 'Fira Code', 'Consolas', monospace; | |
| font-size: 0.875rem; | |
| line-height: 1.5; | |
| } | |
| code { | |
| font-family: inherit; | |
| } | |
| .explanation-section { | |
| background: linear-gradient(135deg, var(--bg-card) 0%, rgba(99, 102, 241, 0.05) 100%); | |
| border-radius: 1rem; | |
| padding: 2rem; | |
| border: 1px solid var(--border); | |
| } | |
| .explanation-grid { | |
| display: grid; | |
| grid-template-columns: 1fr; | |
| gap: 2rem; | |
| margin-top: 1.5rem; | |
| } | |
| @media (min-width: 768px) { | |
| .explanation-grid { | |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | |
| } | |
| } | |
| .explanation-card { | |
| background: rgba(15, 23, 42, 0.5); | |
| padding: 1.5rem; | |
| border-radius: 0.75rem; | |
| border: 1px solid var(--border); | |
| transition: transform 0.3s, box-shadow 0.3s; | |
| } | |
| .explanation-card:hover { | |
| transform: translateY(-4px); | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.2); | |
| } | |
| .card-icon { | |
| width: 48px; | |
| height: 48px; | |
| background: rgba(99, 102, 241, 0.1); | |
| border-radius: 0.75rem; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| margin-bottom: 1rem; | |
| } | |
| .card-icon i { | |
| font-size: 1.5rem; | |
| color: var(--primary); | |
| } | |
| .card-title { | |
| font-size: 1.125rem; | |
| font-weight: 600; | |
| margin-bottom: 0.75rem; | |
| color: var(--text-primary); | |
| } | |
| .card-text { | |
| color: var(--text-secondary); | |
| font-size: 0.875rem; | |
| line-height: 1.6; | |
| } | |
| .features-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | |
| gap: 1.5rem; | |
| margin-top: 2rem; | |
| } | |
| .feature-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| padding: 1rem; | |
| background: rgba(99, 102, 241, 0.05); | |
| border-radius: 0.5rem; | |
| border: 1px solid rgba(99, 102, 241, 0.2); | |
| } | |
| .feature-item i { | |
| color: var(--success); | |
| font-size: 1.25rem; | |
| } | |
| .toast { | |
| position: fixed; | |
| bottom: 2rem; | |
| right: 2rem; | |
| background: var(--success); | |
| color: white; | |
| padding: 1rem 1.5rem; | |
| border-radius: 0.5rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| transform: translateX(400px); | |
| transition: transform 0.3s; | |
| z-index: 1000; | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3); | |
| } | |
| .toast.show { | |
| transform: translateX(0); | |
| } | |
| .tabs { | |
| display: flex; | |
| gap: 0.5rem; | |
| margin-bottom: 1.5rem; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .tab { | |
| background: transparent; | |
| border: none; | |
| color: var(--text-secondary); | |
| padding: 0.75rem 1.5rem; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| font-weight: 500; | |
| position: relative; | |
| } | |
| .tab:hover { | |
| color: var(--text-primary); | |
| } | |
| .tab.active { | |
| color: var(--primary); | |
| } | |
| .tab.active::after { | |
| content: ''; | |
| position: absolute; | |
| bottom: -1px; | |
| left: 0; | |
| right: 0; | |
| height: 2px; | |
| background: var(--primary); | |
| } | |
| .tab-content { | |
| display: none; | |
| } | |
| .tab-content.active { | |
| display: block; | |
| } | |
| .dependencies { | |
| background: rgba(249, 115, 22, 0.1); | |
| border: 1px solid rgba(249, 115, 22, 0.3); | |
| border-radius: 0.5rem; | |
| padding: 1rem; | |
| margin-top: 1rem; | |
| } | |
| .dependencies-title { | |
| color: var(--secondary); | |
| font-weight: 600; | |
| margin-bottom: 0.5rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .dependencies-list { | |
| font-family: 'Fira Code', monospace; | |
| font-size: 0.875rem; | |
| color: var(--text-secondary); | |
| } | |
| @keyframes fadeIn { | |
| from { | |
| opacity: 0; | |
| transform: translateY(20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| .fade-in { | |
| animation: fadeIn 0.5s ease-out; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="header-content"> | |
| <div class="logo"> | |
| <i class="fas fa-code"></i> | |
| <span>Code Converter</span> | |
| </div> | |
| <nav class="nav-links"> | |
| <a href="#converter">Конвертер</a> | |
| <a href="#explanation">Объяснения</a> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="brand-link"> | |
| <i class="fas fa-rocket"></i> Built with anycoder | |
| </a> | |
| </nav> | |
| </div> | |
| </header> | |
| <main> | |
| <section id="converter" class="converter-section fade-in"> | |
| <div class="section-header"> | |
| <h2 class="section-title"> | |
| <i class="fas fa-exchange-alt"></i> | |
| Конвертация C# в Rust | |
| </h2> | |
| <button class="btn-icon" onclick="downloadRustCode()"> | |
| <i class="fas fa-download"></i> Скачать Rust код | |
| </button> | |
| </div> | |
| <div class="tabs"> | |
| <button class="tab active" onclick="switchTab('server')"> | |
| <i class="fas fa-server"></i> UDP Сервер | |
| </button> | |
| <button class="tab" onclick="switchTab('client')"> | |
| <i class="fas fa-desktop"></i> UDP Клиент | |
| </button> | |
| <button class="tab" onclick="switchTab('complete')"> | |
| <i class="fas fa-file-code"></i> Полный код | |
| </button> | |
| </div> | |
| <div id="server-tab" class="tab-content active"> | |
| <div class="code-container"> | |
| <div class="code-block"> | |
| <div class="code-header"> | |
| <span class="language-tag csharp-tag"> | |
| <i class="fab fa-microsoft"></i> C# | |
| </span> | |
| <div class="code-actions"> | |
| <button class="btn-icon" onclick="copyCode('csharp-server')"> | |
| <i class="fas fa-copy"></i> Копировать | |
| </button> | |
| </div> | |
| </div> | |
| <div class="code-content"> | |
| <pre><code class="language-csharp" id="csharp-server">int port_local = 11000; | |
| UdpClient server_local = new UdpClient(port_local); | |
| Console.WriteLine($"UDP сервер запущен на порту {port_local}..."); | |
| IPEndPoint remoteEP_local = new IPEndPoint(IPAddress.Any, 0); | |
| CancellationTokenSource token_local = new CancellationTokenSource(); | |
| Task serverTask_local = Task.Run(() => | |
| { | |
| while (token_local.IsCancellationRequested == false) | |
| { | |
| try | |
| { | |
| byte[] data = server_local.Receive(ref remoteEP_local); | |
| server_local.Send(data, data.Length, remoteEP_local); | |
| string message = Encoding.UTF8.GetString(data); | |
| Console.WriteLine($"Получено от {remoteEP_local}: {message}"); | |
| } | |
| catch (Exception ex) | |
| { | |
| Log.ошибка(ex, "UDP serverTask"); | |
| } | |
| } | |
| });</code></pre> | |
| </div> | |
| </div> | |
| <div class="code-block"> | |
| <div class="code-header"> | |
| <span class="language-tag rust-tag"> | |
| <i class="fab fa-rust"></i> Rust | |
| </span> | |
| <div class="code-actions"> | |
| <button class="btn-icon" onclick="copyCode('rust-server')"> | |
| <i class="fas fa-copy"></i> Копировать | |
| </button> | |
| </div> | |
| </div> | |
| <div class="code-content"> | |
| <pre><code class="language-rust" id="rust-server">use std::net::{UdpSocket, Ipv4Addr, SocketAddr}; | |
| use std::thread; | |
| use std::sync::Arc; | |
| use std::sync::atomic::{AtomicBool, Ordering}; | |
| const PORT: u16 = 11000; | |
| pub fn start_udp_server() -> Arc<AtomicBool> { | |
| let socket = UdpSocket::bind(("0.0.0.0", PORT)) | |
| .expect("Не удалось привязать сокет"); | |
| println!("UDP сервер запущен на порту {}", PORT); | |
| let running = Arc::new(AtomicBool::new(true)); | |
| let running_clone = running.clone(); | |
| thread::spawn(move || { | |
| let mut buf = [0; 1024]; | |
| while running_clone.load(Ordering::Relaxed) { | |
| match socket.recv_from(&mut buf) { | |
| Ok((size, src)) => { | |
| let data = &buf[..size]; | |
| let message = String::from_utf8_lossy(data); | |
| println!("Получено от {}: {}", src, message); | |
| // Отправляем эхо-ответ | |
| if let Err(e) = socket.send_to(data, src) { | |
| eprintln!("Ошибка отправки: {}", e); | |
| } | |
| } | |
| Err(e) => { | |
| eprintln!("Ошибка приёма: {}", e); | |
| } | |
| } | |
| } | |
| }); | |
| running | |
| }</code></pre> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="client-tab" class="tab-content"> | |
| <div class="code-container"> | |
| <div class="code-block"> | |
| <div class="code-header"> | |
| <span class="language-tag csharp-tag"> | |
| <i class="fab fa-microsoft"></i> C# | |
| </span> | |
| <div class="code-actions"> | |
| <button class="btn-icon" onclick="copyCode('csharp-client')"> | |
| <i class="fas fa-copy"></i> Копировать | |
| </button> | |
| </div> | |
| </div> | |
| <div class="code-content"> | |
| <pre><code class="language-csharp" id="csharp-client">UdpClient udp_client_local = new UdpClient(); | |
| Task clientTask_local = Task.Run(async () => | |
| { | |
| try | |
| { | |
| IPEndPoint serverEP = new IPEndPoint(IPAddress.Any, 0); | |
| int messageCounter = 1; | |
| while (token_local.IsCancellationRequested == false) | |
| { | |
| string message = $"Сообщение {messageCounter}"; | |
| byte[] data = Encoding.UTF8.GetBytes(message); | |
| long start_time = Stopwatch.GetTimestamp(); | |
| udp_client_local.Send(data, data.Length, "127.0.0.1", port_local); | |
| byte[] receivedData = udp_client_local.Receive(ref serverEP); | |
| TimeSpan rtt = Stopwatch.GetElapsedTime(start_time); | |
| string response = Encoding.UTF8.GetString(receivedData); | |
| Console.WriteLine($"Ответ от сервера: {response} | RTT: {rtt.TotalMilliseconds.ToString("F3")} ms | PING: {(rtt.TotalMilliseconds / 2).ToString("F3")} ms"); | |
| messageCounter++; | |
| await Task.Delay(1000); | |
| } | |
| } | |
| catch (Exception ex) | |
| { | |
| Log.ошибка(ex, "clientTask"); | |
| } | |
| });</code></pre> | |
| </div> | |
| </div> | |
| <div class="code-block"> | |
| <div class="code-header"> | |
| <span class="language-tag rust-tag"> | |
| <i class="fab fa-rust"></i> Rust | |
| </span> | |
| <div class="code-actions"> | |
| <button class="btn-icon" onclick="copyCode('rust-client')"> | |
| <i class="fas fa-copy"></i> Копировать | |
| </button> | |
| </div> | |
| </div> | |
| <div class="code-content"> | |
| <pre><code class="language-rust" id="rust-client">use std::net::UdpSocket; | |
| use std::time::{Duration, Instant}; | |
| use std::thread; | |
| pub async fn start_udp_client(running: Arc<AtomicBool>) { | |
| let socket = UdpSocket::bind("0.0.0.0:0") | |
| .expect("Не удалось создать клиентский сокет"); | |
| let server_addr: SocketAddr = ("127.0.0.1", PORT).into(); | |
| let mut message_counter = 1; | |
| while running.load(Ordering::Relaxed) { | |
| let message = format!("Сообщение {}", message_counter); | |
| let data = message.as_bytes(); | |
| let start_time = Instant::now(); | |
| // Отправляем сообщение | |
| if let Err(e) = socket.send_to(data, server_addr) { | |
| eprintln!("Ошибка отправки: {}", e); | |
| continue; | |
| } | |
| // Получаем ответ | |
| let mut buf = [0; 1024]; | |
| match socket.recv_from(&mut buf) { | |
| Ok((size, _)) => { | |
| let rtt = start_time.elapsed(); | |
| let response = String::from_utf8_lossy(&buf[..size]); | |
| println!("Ответ от сервера: {} | RTT: {:.3} ms | PING: {:.3} ms", | |
| response, | |
| rtt.as_millis(), | |
| rtt.as_millis() as f64 / 2.0); | |
| } | |
| Err(e) => { | |
| eprintln!("Ошибка приёма: {}", e); | |
| } | |
| } | |
| message_counter += 1; | |
| thread::sleep(Duration::from_millis(1000)); | |
| } | |
| }</code></pre> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="complete-tab" class="tab-content"> | |
| <div class="code-block"> | |
| <div class="code-header"> | |
| <span class="language-tag rust-tag"> | |
| <i class="fab fa-rust"></i> Полный код на Rust | |
| </span> | |
| <div class="code-actions"> | |
| <button class="btn-icon" onclick="copyCode('rust-complete')"> | |
| <i class="fas fa-copy"></i> Копировать | |
| </button> | |
| </div> | |
| </div> | |
| <div class="code-content"> | |
| <pre><code class="language-rust" id="rust-complete">use std::net::{UdpSocket, SocketAddr}; | |
| use std::sync::{Arc, mpsc}; | |
| use std::sync::atomic::{AtomicBool, Ordering}; | |
| use std::thread; | |
| use std::time::{Duration, Instant}; | |
| const PORT: u16 = 11000; | |
| fn main() { | |
| println!("Запуск UDP сервера и клиента..."); | |
| let running = Arc::new(AtomicBool::new(true)); | |
| // Запускаем сервер | |
| let server_running = start_udp_server(running.clone()); | |
| // Запускаем клиент в отдельном потоке | |
| let client_running = running.clone(); | |
| let client_handle = thread::spawn(move || { | |
| let rt = tokio::runtime::Runtime::new().unwrap(); | |
| rt.block_on(start_udp_client(client_running)); | |
| }); | |
| println!("Нажмите Enter для остановки..."); | |
| let mut input = String::new(); | |
| std::io::stdin().read_line(&mut input).unwrap(); | |
| // Останавливаем сервер и клиент | |
| running.store(false, Ordering::Relaxed); | |
| // Ждём завершения потоков | |
| thread::sleep(Duration::from_secs(3)); | |
| println!("Программа завершена."); | |
| } | |
| pub fn start_udp_server(running: Arc<AtomicBool>) -> Arc<AtomicBool> { | |
| let socket = UdpSocket::bind(("0.0.0.0", PORT)) | |
| .expect("Не удалось привязать сокет"); | |
| println!("UDP сервер запущен на порту {}", PORT); | |
| let server_running = running.clone(); | |
| thread::spawn(move || { | |
| let mut buf = [0; 1024]; | |
| while server_running.load(Ordering::Relaxed) { | |
| match socket.recv_from(&mut buf) { | |
| Ok((size, src)) => { | |
| let data = &buf[..size]; | |
| let message = String::from_utf8_lossy(data); | |
| println!("Получено от {}: {}", src, message); | |
| if let Err(e) = socket.send_to(data, src) { | |
| eprintln!("Ошибка отправки: {}", e); | |
| } | |
| } | |
| Err(e) => { | |
| eprintln!("Ошибка приёма: {}", e); | |
| } | |
| } | |
| } | |
| }); | |
| running | |
| } | |
| pub async fn start_udp_client(running: Arc<AtomicBool>) { | |
| let socket = UdpSocket::bind("0.0.0.0:0") | |
| .expect("Не удалось создать клиентский сокет"); | |
| let server_addr: SocketAddr = ("127.0.0.1", PORT).into(); | |
| let mut message_counter = 1; | |
| while running.load(Ordering::Relaxed) { | |
| let message = format!("Сообщение {}", message_counter); | |
| let data = message.as_bytes(); | |
| let start_time = Instant::now(); | |
| if let Err(e) = socket.send_to(data, server_addr) { | |
| eprintln!("Ошибка отправки: {}", e); | |
| continue; | |
| } | |
| let mut buf = [0; 1024]; | |
| match socket.recv_from(&mut buf) { | |
| Ok((size, _)) => { | |
| let rtt = start_time.elapsed(); | |
| let response = String::from_utf8_lossy(&buf[..size]); | |
| println!("Ответ от сервера: {} | RTT: {:.3} ms | PING: {:.3} ms", | |
| response, | |
| rtt.as_millis(), | |
| rtt.as_millis() as f64 / 2.0); | |
| } | |
| Err(e) => { | |
| eprintln!("Ошибка приёма: {}", e); | |
| } | |
| } | |
| message_counter += 1; | |
| thread::sleep(Duration::from_millis(1000)); | |
| } | |
| }</code></pre> | |
| </div> | |
| <div class="dependencies"> | |
| <div class="dependencies-title"> | |
| <i class="fas fa-cube"></i> Зависимости для Cargo.toml | |
| </div> | |
| <div class="dependencies-list"> | |
| [dependencies] | |
| tokio = { version = "1.0", features = ["full"] } | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <section id="explanation" class="explanation-section fade-in"> | |
| <div class="section-header"> | |
| <h2 class="section-title"> | |
| <i class="fas fa-graduation-cap"></i> | |
| Ключевые отличия и особенности | |
| </h2> | |
| </div> | |
| <div class="explanation-grid"> | |
| <div class="explanation-card"> | |
| <div class="card-icon"> | |
| <i class="fas fa-memory"></i> | |
| </div> | |
| <h3 class="card-title">Управление памятью</h3> | |
| <p class="card-text"> | |
| Rust обеспечивает безопасность памяти без сборщика мусора. Вместо C# GC, Rust использует владение и заимствование для предотвращения ошибок. | |
| </p> | |
| </div> | |
| <div class="explanation-card"> | |
| <div class="card-icon"> | |
| <i class="fas fa-sync-alt"></i> | |
| </div> | |
| <h3 class="card-title">Асинхронность</h3> | |
| <p class="card-text"> | |
| Вместо Task.Run из C#, Rust использует async/await с рантаймом tokio. Асинхронный код в Rust компилируется в state machine. | |
| </p> | |
| </div> | |
| <div class="explanation-card"> | |
| <div class="card-icon"> | |
| <i class="fas fa-shield-alt"></i> | |
| </div> | |
| <h3 class="card-title">Обработка ошибок</h3> | |
| <p class="card-text"> | |
| Rust использует Result<T, E> вместо исключений C#. Это заставляет обрабатывать все возможные ошибки компилятором. | |
| </p> | |
| </div> | |
| <div class="explanation-card"> | |
| <div class="card-icon"> | |
| <i class="fas fa-tachometer-alt"></i> | |
| </div> | |
| <h3 class="card-title">Производительность</h3> | |
| <p class="card-text"> | |
| Код Rust компилируется в машинный код без накладных расходов рантайма, обеспечивая производительность на уровне C/C++. | |
| </p> | |
| </div> | |
| </div> | |
| <h3 style="margin-top: 2rem; margin-bottom: 1rem; font-size: 1.25rem; color: var(--text-primary);"> | |
| <i class="fas fa-list-check" style="color: var(--primary); margin-right: 0.5rem;"></i> | |
| Преимущества конвертации в Rust | |
| </h3> | |
| <div class="features-grid"> | |
| <div class="feature-item"> | |
| <i class="fas fa-check-circle"></i> | |
| <span>Нулевая стоимость абстракций</span> | |
| </div> | |
| <div class="feature-item"> | |
| <i class="fas fa-check-circle"></i> | |
| <span>Безопасные потоки без data races</span> | |
| </div> | |
| <div class="feature-item"> | |
| <i class="fas fa-check-circle"></i> | |
| <span>Детерминированное управление ресурсами</span> | |
| </div> | |
| <div class="feature-item"> | |
| <i class="fas fa-check-circle"></i> | |
| <span>WebAssembly поддержка</span> | |
| </div> | |
| <div class="feature-item"> | |
| <i class="fas fa-check-circle"></i> | |
| <span>Отличная экосистема (Cargo)</span> | |
| </div> | |
| <div class="feature-item"> | |
| <i class="fas fa-check-circle"></i> | |
| <span>Безопасность без потери производительности</span> | |
| </div> | |
| </div> | |
| </section> | |
| </main> | |
| <div id="toast" class="toast"> | |
| <i class="fas fa-check-circle"></i> | |
| <span id="toast-message">Скопировано в буфер обмена!</span> | |
| </div> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-csharp.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-rust.min.js"></script> | |
| <script> | |
| function switchTab(tabName) { | |
| // Hide all tabs | |
| document.querySelectorAll('.tab-content').forEach(tab => { | |
| tab.classList.remove('active'); | |
| }); | |
| // Remove active class from all tab buttons | |
| document.querySelectorAll('.tab').forEach(btn => { | |
| btn.classList.remove('active'); | |
| }); | |
| // Show selected tab | |
| document.getElementById(tabName + '-tab').classList.add('active'); | |
| // Add active class to clicked button | |
| event.target.classList.add('active'); | |
| // Re-highlight code | |
| Prism.highlightAll(); | |
| } | |
| function copyCode(codeId) { | |
| const codeElement = document.getElementById(codeId); | |
| const text = codeElement.textContent; | |
| navigator.clipboard.writeText(text).then(() => { | |
| showToast('Код скопирован в буфер обмена!'); | |
| }).catch(err => { | |
| console.error('Ошибка копирования:', err); | |
| showToast('Ошибка копирования кода', 'error'); | |
| }); | |
| } | |
| function showToast(message, type = 'success') { | |
| const toast = document.getElementById('toast'); | |
| const toastMessage = document.getElementById('toast-message'); | |
| toastMessage.textContent = message; | |
| if (type === 'error') { | |
| toast.style.background = 'var(--error)'; | |
| } else { | |
| toast.style.background = 'var(--success)'; | |
| } | |
| toast.classList.add('show'); | |
| setTimeout(() => { | |
| toast.classList.remove('show'); | |
| }, 3000); | |
| } | |
| function downloadRustCode() { | |
| const rustCode = document.getElementById('rust-complete').textContent; | |
| const cargoToml = `[dependencies]\ntokio = { version = "1.0", features = ["full"] }`; | |
| const content = `// main.rs\n${rustCode}\n\n// Cargo.toml\n${cargoToml}`; | |
| const blob = new Blob([content], { type: 'text/plain' }); | |
| const url = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = 'udp_server_client_rust.txt'; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| window.URL.revokeObjectURL(url); | |
| showToast('Rust код загружен!'); | |
| } | |
| // Add smooth scrolling | |
| document.querySelectorAll('a[href^="#"]').forEach(anchor => { | |
| anchor.addEventListener('click', function (e) { | |
| e.preventDefault(); | |
| const target = document.querySelector(this.getAttribute('href')); | |
| if (target) { | |
| target.scrollIntoView({ | |
| behavior: 'smooth', | |
| block: 'start' | |
| }); | |
| } | |
| }); | |
| }); | |
| // Add fade-in animation on scroll | |
| const observerOptions = { | |
| threshold: 0.1, | |
| rootMargin: '0px 0px -50px 0px' | |
| }; | |
| const observer = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| entry.target.style.opacity = '1'; | |
| entry.target.style.transform = 'translateY(0)'; | |
| } | |
| }); | |
| }, observerOptions); | |
| document.querySelectorAll('.fade-in').forEach(el => { | |
| el.style.opacity = '0'; | |
| el.style.transform = 'translateY(20px)'; | |
| el.style.transition = 'opacity 0.5s ease-out, transform 0.5s ease-out'; | |
| observer.observe(el); | |
| }); | |
| // Initialize syntax highlighting | |
| Prism.highlightAll(); | |
| </script> | |
| </body> | |
| </html> |