మీరు ఒక కొత్త భాషను నేర్చుకోవడం మొదలుపెట్టినప్పుడు, వేగంగా పరిగెత్తవచ్చని ఆశించి, కానీ మొదటి అడుగులోనే తడబడినప్పుడు కలిగే ఆ అనుభవం మీకు తెలుసా? Rust విషయంలో నా పరిస్థితి కూడా అలాగే ఉంది. నా దగ్గర ఒక చక్కని JavaScript యుటిలిటీ ఉంది — అది కొన్ని arraysలను parse చేసి, వాటిని మార్చే (mutate) పని చేసేది — ఒక కాఫీ బ్రేక్ సమయంలో దాన్ని మళ్ళీ రాసేయవచ్చని నేను అనుకున్నాను. నేను నా ఎడిటర్ను తెరిచి, సహజంగా అనిపించే కోడ్ను టైప్ చేసి, కంపైలర్ను రన్ చేశాను. అది పేలిపోయింది. సింటాక్స్ ఎర్రర్స్ (syntax errors) వల్ల కాదు, లేదా సెమీకోలన్స్ (semicolons) మిస్ అవ్వడం వల్ల కూడా కాదు. నేను ఇప్పటికే 'move' చేయబడిన ఒక విలువను (value) ఉపయోగించడానికి ప్రయత్నించానని అది నాకు చెప్పింది. నేను స్క్రీన్ వైపు చూస్తూ ఉండిపోయాను. Moved? నేను దేనినీ మూవ్ చేయలేదు. నేను దానిని కేవలం మరొక వేరియబుల్కు అసైన్ చేశాను అంతే.
JavaScript కంఫర్ట్ జోన్
డేటాను ఒక సామూహిక వైట్బోర్డ్లా పరిగణించాలని JavaScript మిమ్మల్ని అలవాటు చేస్తుంది. మీరు ఒక ఆబ్జెక్ట్ లేదా ఒక arrayని డిక్లేర్ చేసి, దానిని ఒక ఫంక్షన్కు ఇస్తారు, ఆ ఫంక్షన్ అందులో ఒక ప్రాపర్టీని యాడ్ చేస్తుంది, ఆపై దానిని వేరే చోటికి పంపిస్తారు. Garbage collector అనేది ఒక అదృశ్య పారిశుధ్య కార్మికుడిలా బ్యాక్గ్రౌండ్లో ఉండి, మీరు వదిలేసిన వాటిని శుభ్రం చేయడానికి వేచి చూస్తుంది. "ఈ స్ట్రింగ్ను ఎవరు యాజమాన్యం (own) చేస్తున్నారు?" అని మీరు ఎప్పుడూ అడగరు. "నేను దీన్ని చదవవచ్చా?" అని మాత్రమే అడుగుతారు. సమాధానం 'అవును' అయితే, మీరు దానిని ఉపయోగిస్తారు. మీకు మరొక రిఫరెన్స్ కావాలంటే, మీరు కేవలం let b = a అని రాసి ముందుకు వెళ్ళిపోతారు. రెండు వేరియబుల్స్ ఒకే మెమరీ భాగాన్ని సూచిస్తాయి, ఒకవేళ a దానిని మారుస్తే (mutate), b కూడా ఆ మార్పును వెంటనే చూస్తుంది. ఇది చాలా సౌకర్యవంతంగా ఉంటుంది. ఇది గందరగోళంగా ఉన్నప్పటికీ, రన్టైమ్ (runtime) క్లీనప్ను చూసుకుంటుంది కాబట్టి ఆ గందరగోళం వల్ల మీకు పెద్దగా నష్టం జరగదు.
మొదటి కంపైలర్ షాక్
Rust మిమ్మల్ని నమ్మదు. ఇది వినడానికి కఠినంగా అనిపించవచ్చు, కానీ మీరు నేర్చుకునే మొదటి విషయం ఇదే. ఈ భాష మూడు కఠినమైన నియమాలతో కూడిన 'ownership system' చుట్టూ నిర్మించబడింది. మొదటిది, ప్రతి విలువకు ఖచ్చితంగా ఒకే ఒక యజమాని (owner) ఉంటారు. రెండవది, ఒక సమయంలో ఒకే యజమాని ఉండాలి. మూడవది, ఆ యజమాని స్కోప్ (scope) నుండి బయటకు వచ్చినప్పుడు, Rust ఆ విలువను ఆటోమేటిక్గా డ్రాప్ (drop) చేస్తుంది. ఇక్కడ Garbage collector ఉండదు. మీరు స్పష్టంగా కోరుకోనంత వరకు బ్యాక్గ్రౌండ్లో రిఫరెన్స్ కౌంటింగ్ (reference counting) ఉండదు. మీ కోడ్ రన్ అవ్వకముందే, కంపైలర్ ఈ మూడు నియమాలను అమలు చేస్తుంది.
నేను ఆ JavaScript యుటిలిటీ యొక్క Rust వెర్షన్ను రాస్తున్నప్పుడు, నాకు సహజంగా అనిపించినట్లే చేశాను. నేను ఒక vectorను (ఇది arrayకి Rust ఇచ్చే సమాధానం) సృష్టించి, దానిని రెండవ వేరియబుల్కు అసైన్ చేశాను. ఆపై మొదటి వేరియబుల్ను ఉపయోగించడానికి ప్రయత్నించాను. కంపైలర్ నిరాకరించింది. JavaScriptలో, let b = a అనేది రిఫరెన్స్ను కాపీ చేస్తుంది. కానీ Rustలో, అది యాజమాన్యాన్ని (ownership) మారుస్తుంది (moves). అసలు వేరియబుల్ చెల్లనిదిగా (invalid) మారుతుంది. మీరు పొరపాటున ఒకే మెమరీపై రెండు మార్గాలు ఒకేసారి ప్రభావం చూపకుండా ఉండటానికి కంపైలర్ ఇలా చేస్తుంది; ఇతర సిస్టమ్స్లో data races మరియు use-after-free బగ్స్ ఎలా వస్తాయో, అలా రాకుండా ఇది నిరోధిస్తుంది.
Rust మీ వస్తువులను ఎందుకు లాగేసుకుంటుంది?
JavaScriptలో ఈ ప్యాటర్న్ను చూడండి:
let a = [1, 2, 3];
let b = a;
a.push(4);
// Both a and b see 4.
ఇది మీకు అలవాటుగా ఉంటుంది. మీరు దీని గురించి రెండోసారి ఆలోచించరు. కానీ Rustలో, మీరు టైప్ చేయడం పూర్తికాకముందే అదే అంతర్ దృష్టి (intuition) వల్ల కంపైలర్ మిమ్మల్ని తిరస్కరిస్తుంది. ఒకసారి b ఆ vectorను యాజమాన్యం చేస్తే, a అనేది కేవలం ఒక ఖాళీ పేరు మాత్రమే. మీరు దానిలోకి ఏదీ పుష్ (push) చేయలేరు. మీరు దానిని చూడలేరు కూడా. ఆ మెమరీ ఇప్పుడు b కి చెందుతుంది.
ఇది ఒక శిక్షలా అనిపించవచ్చు, కానీ ఇది దేనిని నిరోధిస్తుందో మీరు గ్రహించిన తర్వాత మీ అభిప్రాయం మారుతుంది. ఒకవేళ రెండు వేరియబుల్స్ సమన్వయం లేకుండా ఒకే హీప్ (heap) డేటాను మార్చగలిగితే, మెమరీ కరప్షన్ (memory corruption) జరిగే ప్రమాదం ఉంది. ఈ మార్పిడిని స్పష్టంగా చేయడం ద్వారా Rust ఆ రకమైన బగ్స్ను పూర్తిగా తొలగిస్తుంది. కంపైలర్ ఇక్కడ అనవసరంగా నియమాలు పాటించడం లేదు. అది ఒక గేట్కీపర్లా పనిచేస్తోంది. మీ కోడ్లోని ఏ భాగం ఏ డేటాకు బాధ్యత వహించాలో ప్రతి దశలోనూ మీరు నిర్ణయించుకునేలా అది మిమ్మల్ని బలవంతం చేస్తుంది.
Borrowing: నిజానికి ఒక ఫీచర్గా ఉండే పరిష్కారం
అన్ని అసైన్మెంట్లు యాజమాన్యాన్ని శాశ్వతంగా మార్చేస్తే, ప్రోగ్రామ్లు రాయడం చాలా అలసటగా మారుతుంది. మీరు ప్రతిదీ క్లోన్ (clone) చేయాల్సి ఉంటుంది, దీనివల్ల మెమరీ మరియు వేగం వృథా అవుతాయి. Rust దీనిని 'borrowing' ద్వారా పరిష్కరిస్తుంది.
Borrowing అనేది ఒక విలువను తీసుకోవడం (taking) లేకుండానే దానిని ఉపయోగించుకోవడానికి అనుమతిస్తుంది. ఇందులో రెండు రకాలు ఉన్నాయి, మరియు వాటి మధ్య తేడా చాలా ముఖ్యం.
- Immutable borrows, వీటిని
&Tఅని రాస్తారు: ఇవి మీకు డేటాను చదవడానికి అనుమతిస్తాయి. మీకు ఎన్ని కావాలంటే అన్నింటిని ఒకే సమయంలో...
