Rust का सबसे सूक्ष्म syntax
(zkrising.com)Rust में let और const
letका उपयोग नया variable declare करने के लिए होता है- इसका रूप
let PAT = EXPR;होता है, और यह जितना दिखता है उससे ज़्यादा शक्तिशाली है - pattern matching के साथ मिलकर यह सुविधाजनक features देता है
let (a, b) = (5, 10);let maybe_string: Option<String> = ..;let Some(value) = maybe_string else { panic!("die horribly")};
- इसका रूप
constवे constants हैं जो compile time पर calculate होते हैं और compiled code में सीधे शामिल हो जाते हैंconst MY_VAR: &str = "heyyyyyyyy man";const SECRET: i32 = 0x1234;- इसका रूप
const IDENT: TYPE = EXPR;होता है, इसमें type लिखना ज़रूरी है और pattern का उपयोग नहीं किया जा सकता
भ्रम पैदा करने वाली बात
constको declaration order की परवाह किए बिना इस्तेमाल किया जा सकता है (hoisting)
// X को Y के बाद define किया गया है, फिर भी compile हो जाता है
const Y: i32 = X + X;
const X: i32 = 5;
- इसे function के अंदर भी declare किया जा सकता है, और वहाँ भी hoisting काम करती है
fn oh_boy() -> i32 {
return X;
const X: i32 = 5;
// ^ यह compile होता है और काम भी करता है. कोई warning नहीं!
}
- अगर आप ऐसे programmer के साथ काम कर रहे हैं जो JavaScript से आया है और अभी Rust सीखना शुरू ही कर रहा है, तो यह feature उसे काफ़ी उलझा सकता है
- यह एक अच्छे feature का हानिरहित नतीजा है, लेकिन अब इसके हानिकारक नतीजों की बात करते हैं
Rust में Match
// let PAT = EXPR;
let x = 5;
// यहाँ `x` एक pattern है. यह जाँचा जाता है कि क्या `5` को `x` में रखा जा सकता है
// यह pattern हमेशा match करता है -- 5 को हमेशा `x` नाम के variable में रखा जा सकता है
// हर pattern का match होना ज़रूरी नहीं है. उदाहरण के लिए:
let (5, x) = (a, b);
// यहाँ expression तभी pattern से "match" करता है जब a == 5 हो
//
// इसे "refutable" pattern कहा जाता है
//
// `let` declaration में, refutable pattern को "reject" होने की स्थिति संभालनी पड़ती है:
let (5, x) = (a, b) else { panic!() };
//
// ...वरना आपके पास ऐसा variable हो सकता है जो "conditionally existing" हो, और यह अच्छी बात नहीं है
- तो अब
matchको देखते हैं.matchक्या है?
// match, patterns और उनके match होने पर किए जाने वाले कामों की एक सूची है
//
// match EXPR {
// PAT => EXPR
// PAT => EXPR
// ..
// }
match (a, b) {
(5, x) => {
// अगर (a,b), (5,x) से match करता है, तो यह block चलेगा
},
(x, 5) => {
// इसी तरह: अगर (a,b), (x, 5) से match करता है..
},
(x, y) => {
// और यह एक "catch-all" pattern है, ठीक वैसे ही जैसे let (x,y) = (a,b) काम करता है
}
}
अब दर्द देते हैं
- लोगों को confuse करना मज़ेदार है, लेकिन क्या हो अगर इससे पूरी बदहाली और असली bugs पैदा हों?
- मेरी नज़र में यह Rust का सबसे सूक्ष्म syntax है:
- इस लेख की सबसे दिलचस्प पंक्ति: Rust का सबसे सूक्ष्म syntax यह है कि constants खुद pattern होते हैं
- यह syntax matching के आसपास कुछ अच्छे ergonomics जोड़ता है:
let input: i32 = ..;
const GOOD: i32 = 1;
const BAD: i32 = 2;
match input {
// यह जाँचता है कि input == GOOD है या नहीं, क्योंकि GOOD एक constant है
GOOD => println!("input was 1"),
// यह जाँचता है कि input == BAD है या नहीं, क्योंकि BAD भी constant है
BAD => println!("input was 2"),
// यह otherwise = input define करता है, और हमेशा match करता है...
otherwise => println!("input was {otherwise}"),
}
लेकिन constants को uppercase में लिखना सिर्फ एक convention है. ऐसा न करने पर compiler केवल warning देता है.
const good: i32 = 1;
const bad: i32 = 2;
match input {
// उhm...
good => {},
bad => {},
otherwise => {},
}
अब हमारे पास तीन branches हैं जो एक जैसी दिखती हैं, लेकिन वे क्या करती हैं यह इस बात पर निर्भर करता है कि उस नाम का constant मौजूद है या नहीं!
चलो इसे और ख़राब बनाते हैं. नीचे क्या होगा?
const GOOD: i32 = 1;
match input {
// typo...
GOD => println!("input was 1"),
otherwise => println!("input was not 1")
}
यहाँ compiler warning देगा, लेकिन यह code हमेशा input was 1 print करेगा
या थोड़ा और वास्तविक उदाहरण:
// ओह, गलती से यह import comment out या delete कर दिया
// use crate::{SOME_GL_CONSTANT, OTHER_THING}
// अरे नहीं!
match value {
SOME_GL_CONSTANT => ..,
OTHER_THING => ..,
_ => ..,
}
यह लोगों को confuse करता है. खासकर जब वे enums के साथ कुछ स्मार्ट करने की कोशिश कर रहे हों.
enum MyEnum {
A, B, C
}
// आमतौर पर इसे ऐसे लिखा जाता है
match value {
MyEnum::A => ..,
MyEnum::B => ..,
MyEnum::C => ..,
}
// लेकिन इसे ऐसे भी लिखा जा सकता है
use MyEnum::*;
match value {
A => {},
B => {},
C => {}
}
// और फिर, अगर आप MyEnum को बदल दें...
enum MyEnum { A, B, D, E };
use MyEnum::*;
// यह अब भी compile होता है!
match value {
A => {},
B => {},
C => {},
}
// `C` अब एक "catch-all" pattern बन जाता है, क्योंकि `C` जैसा कुछ scope में नहीं है.
// आप असल में let C = value कर रहे हैं, और यह हमेशा match करता है!!!
Clippy में ऐसे कामों से बचने की चेतावनी देने वाले कई rules हैं, क्योंकि यह लोगों को बार-बार confuse करता है.
लेकिन इसे और भी ज़्यादा confusing बनाया जा सकता है:
// x को 5 से irrefutably bind करें...
let x = 5;
// ...एक मिनट...
const x: i32 = 4;
यह code compile नहीं होगा. क्योंकि const x एक pattern है, constants hoist होते हैं, और अब इस code का मूल्यांकन ऐसे होता है:
let 4 = 5;
// error[E0005]: refutable pattern in local binding
// --> src/main.rs:3:5
// |
// 3 | let x = 5;
// | ^
// | |
// | pattern `i32::MIN..=3_i32` और `5_i32..=i32::MAX` cover नहीं किए गए
// | missing patterns इसलिए cover नहीं हुए क्योंकि `x` को नए variable की जगह constant pattern माना गया
// | help: इसके बजाय एक variable introduce करें: `x_var`
// |
// = note: `let` bindings को "irrefutable pattern" चाहिए होता है, जैसे `struct` या ऐसा `enum` जिसमें सिर्फ एक variant हो
"expr 4 के बराबर है" कोई irrefutable match नहीं है, क्योंकि यह उस स्थिति को handle नहीं करता जब ऐसा न हो
आसपास सबको परेशान करना
// मान लें `maybe` का type Option<&str> है. इसमें कोई text हो सकता है, या None हो सकता है.
let maybe_username: Option<&str> = ..;
// यह one-line match में Rust का सामान्य pattern है. अगर यह Some(..) से match करे, तो हम उस string के साथ कुछ कर सकते हैं.
if let Some(username) = maybe_username {
// तो यह code तब चलेगा जब username मौजूद होगा...
return username.to_uppercase();
}
// लेकिन ज़रा ठहरिए... अब यह code केवल तब चलेगा जब 'username' Some("hey") से match करे
const username: &str = "hey";
constant hoisting और constants के pattern होने का यह मेल आपको पहेली-जैसा Rust code लिखने देता है
यह असल में कोई बड़ी समस्या नहीं है
- व्यवहारिक रूप से, यह confusing सिर्फ इसलिए हो सकता है क्योंकि आप
let UPPERCASEऔरconst lowercaseलिख सकते हैं - अगर uppercase से शुरू होने वाले variable बनाना lint error होता, तो यह confusion होता ही नहीं
- enum variant या constant से match करने की कोशिश में आप गलती से किसी चीज़ को bind नहीं कर पाते
- लेकिन स्पष्ट कर दें, यह भाषा की बस एक मज़ेदार विचित्रता है
macro_rules! f {
($cond: expr) => {
if let Some(x) = $cond {
println!("i am some == {x}!");
} else {
println!("i am none");
}
}
}
fn main() {
f!(Some(100));
{
f!(Some(100));
return;
const x: i32 = 5;
}
}
3 टिप्पणियां
असल में यह कोई बड़ी समस्या नहीं है, क्योंकि ज़्यादातर development environments में language server होता है
और वह सब कुछ infer करके दिखा देता है
RustRover के language server की बुनियाद rust-analyzer है, जो काफ़ी शक्तिशाली tool है
बस अलग-अलग भाषाओं में मौजूद dark patterns को इकट्ठा करके
यह भ्रम पैदा कर सकता है!
कुछ ऐसा ही एहसास देने वाला लेख है
अरे... ये तो काफ़ी अजीब लग रहा है। Rust इसका क्या प्लान बना रहा होगा?