Solidity की तरह Cairo में “internal” और “pure” modifiers (या इस मामले में कोई अन्य modifiers) नहीं होते हैं।
याद रखें कि किसी impl ब्लॉक को #[abi(embed_v0)] के साथ मार्क करने से Cairo को इसके functions को कॉन्ट्रैक्ट के ABI (Application Binary Interface) में शामिल करने का निर्देश मिलता है, जिससे उन्हें कॉन्ट्रैक्ट के बाहर से कॉल किया जा सकता है। इस impl ब्लॉक के functions को उस trait में परिभाषित किया जाना चाहिए जिसे यह लागू (implement) करता है (Solidity में interfaces के समान); यह सुनिश्चित करता है कि बाहरी कॉलर ठीक से जानते हैं कि कौन से functions उपलब्ध हैं और उन्हें कैसे कॉल करना है।
लेकिन उन functions का क्या जिन्हें बाहरी रूप से कॉल करने योग्य नहीं होना चाहिए? Solidity की तरह ही, Cairo भी functions क्या कर सकते हैं और क्या नहीं, इसके साथ-साथ उनकी function visibility को प्रतिबंधित करने में सक्षम है।
इस लेख में, हम दिखाएंगे कि Cairo में internal, private, और pure functions के समकक्ष (equivalent) कैसे प्राप्त किया जाए।
Internal function का प्रदर्शन (Demonstration)
हमारा पहला प्रदर्शन एक internal view function का है, जो कॉन्ट्रैक्ट स्टेट को देख सकता है लेकिन कॉन्ट्रैक्ट के बाहर से कॉल करने योग्य नहीं है।
डेमो के साथ शुरू करने के लिए, internal_demo नामक एक खाली फोल्डर बनाएं और एक नया Cairo प्रोजेक्ट इनिशियलाइज़ करने के लिए इसके अंदर scarb init रन करें।
इसके बाद, src/lib.cairo के अंदर एक function get_balance_2x() जोड़ें, जैसा कि नीचे दिखाया गया है:
// IHelloStarknet INTERFACE
#[starknet::interface]
pub trait IHelloStarknet<TContractState> {
fn increase_balance(ref self: TContractState, amount: felt252);
fn get_balance(self: @TContractState) -> felt252;
}
#[starknet::contract]
mod HelloStarknet {
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
#[storage]
struct Storage {
balance: felt252,
}
#[abi(embed_v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
// ... existing functions (increase_balance, get_balance) ...
// NEWLY ADDED FUNCTION
// Note: This function will throw error
fn get_balance_2x(self: @ContractState) -> felt252 {
self.balance.read() * 2
}
}
}
हमें एक compilation error मिलता है क्योंकि get_balance_2x, IHelloStarknet का हिस्सा नहीं है।

जैसा कि पहले बताया गया है, Cairo में किसी trait को लागू (implement) करते समय, impl ब्लॉक में केवल उसी trait में परिभाषित functions हो सकते हैं। एक कॉन्ट्रैक्ट में कई impl ब्लॉक हो सकते हैं, और जो functions trait का हिस्सा नहीं हैं उन्हें अलग impl ब्लॉक में परिभाषित किया जाना चाहिए। यह Solidity से अलग है, जहां कॉन्ट्रैक्ट्स उन इंटरफेस से परे भी स्वतंत्र रूप से functions जोड़ सकते हैं जिन्हें वे लागू करते हैं।
हालाँकि, हम विशेष रूप से get_balance_2x को IHelloStarknet trait में शामिल नहीं करना चाहते हैं क्योंकि इससे function public हो जाएगा।
get_balance_2x को HelloStarknetImpl ब्लॉक के अंदर शामिल करने (बिना इसे trait में जोड़े) के कारण होने वाले compilation error का समाधान यह है कि:
get_balance_2xको एक अलगimplब्लॉक में रखें- उस
implब्लॉक को एक अलग trait का उपयोग करने दें।
HelloStarknet कॉन्ट्रैक्ट मॉड्यूल के अंदर, HelloStarknetImpl implementation के बाद निम्नलिखित कोड जोड़ें:
// NEWLY ADDED //
#[generate_trait]
impl InternalFunction of IInternal {
fn get_balance_2x(self: @ContractState) -> felt252 {
self.balance.read() * 2
}
}
पूरा कॉन्ट्रैक्ट नीचे दिखाया गया है जिसमें नया जोड़ा गया InternalFunction impl ब्लॉक लाल रंग में हाइलाइट किया गया है:

InternalFunctionनाम पूरी तरह से मनमाना (arbitrary) है; यह कोई भी नाम हो सकता है जो कॉन्ट्रैक्ट के लिए उपयुक्त हो।- चूँकि प्रत्येक
implब्लॉक को एक associated trait की आवश्यकता होती है, इसलिए हम इसेIInternalनाम देते हैं (यह भी मनमाना है)। - हमें internal
implके लिए स्पष्ट रूप से trait बनाने की आवश्यकता नहीं है। कंपाइलर इसे#[generate_trait]एट्रिब्यूट के साथ स्वचालित रूप से जनरेट करता है।
अब यदि हम टेस्ट्स (tests/test_contract.cairo) से get_balance_2x को एक्सेस करने का प्रयास करते हैं,
#[test]
fn test_balance_2x() {
let contract_address = deploy_contract("HelloStarknet");
let dispatcher = IHelloStarknetDispatcher { contract_address };
let balance_before = dispatcher.get_balance();
assert(balance_before == 0, 'Invalid balance');
dispatcher.increase_balance(42);
let balance_after = dispatcher.get_balance_2x();
assert(balance_after == 42, 'Invalid balance');
}
तो टेस्ट कंपाइल नहीं होगा, क्योंकि वह function सार्वजनिक (publicly) रूप से दिखाई नहीं देता है:

यह टेस्ट करने के लिए कि internal function उम्मीद के मुताबिक काम करता है, हम एक और function extern_wrap_get_balance_2x जोड़ेंगे, जो public होगा, फिर नीचे दिखाए गए अनुसार self वेरिएबल के माध्यम से अपने internal function को एक्सेस करेंगे।
यह न भूलें कि हमें इस function को इंटरफ़ेस में भी जोड़ना होगा (जैसा कि नीचे लाल बॉक्स में देखा गया है) क्योंकि हम चाहते हैं कि यह कॉन्ट्रैक्ट के बाहर एक्सेस करने योग्य हो:

extern_wrap_balance_2x function (नीला बॉक्स) internal function (हरा बॉक्स) को कॉल करता है जो वर्तमान बैलेंस का दोगुना रिटर्न करता है। यहाँ पूरा कोड है:
#[starknet::interface]
pub trait IHelloStarknet<TContractState> {
fn increase_balance(ref self: TContractState, amount: felt252);
fn get_balance(self: @TContractState) -> felt252;
/// Retrieve 2x the balance
fn extern_wrap_get_balance_2x(self: @TContractState) -> felt252;
}
/// Simple contract for managing balance.
#[starknet::contract]
mod HelloStarknet {
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
#[storage]
struct Storage {
balance: felt252,
}
#[abi(embed_v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
fn increase_balance(ref self: ContractState, amount: felt252) {
assert(amount != 0, 'Amount cannot be 0');
self.balance.write(self.balance.read() + amount);
}
fn get_balance(self: @ContractState) -> felt252 {
self.balance.read()
}
fn extern_wrap_get_balance_2x(self: @ContractState) -> felt252 {
self.get_balance_2x()
}
}
#[generate_trait]
impl InternalFunction of IInternal {
fn get_balance_2x(self: @ContractState) -> felt252 {
self.balance.read() * 2
}
}
}
मौजूद टेस्ट्स के नीचे tests/test_contract.cairo में निम्नलिखित टेस्ट जोड़ें:
#[test]
fn test_balance_2x() {
// Deploy the HelloStarknet contract
// Note: deploy_contract is a helper function from the test setup
let contract_address = deploy_contract("HelloStarknet");
// Create a dispatcher to interact with the contract
let dispatcher = IHelloStarknetDispatcher { contract_address };
// check initial balance is 0
let balance_before = dispatcher.get_balance();
assert(balance_before == 0, 'Invalid balance');
// Increase balance by 1
dispatcher.increase_balance(1);
// Call the wrapper function that uses our internal function
// Should return 1 * 2 = 2
let balance_after_2x = dispatcher.extern_wrap_get_balance_2x();
assert(balance_after_2x == 2, 'Invalid balance');
}
scarb test test_balance_2x के साथ टेस्ट रन करें; आपको देखना चाहिए कि यह पास हो जाता है।
संक्षेप में कहें तो: Cairo में internal functions को बिना #[abi(embed_v0)] के एक अलग impl ब्लॉक में परिभाषित करके और #[generate_trait] का उपयोग करके उस trait को स्वतः-जनरेट (auto-generate) करने के लिए बनाया जाता है जिसे impl ब्लॉक लागू करता है। यह functions को आपके कॉन्ट्रैक्ट के भीतर कॉल करने योग्य रखता है लेकिन बाहरी कॉलर्स से छिपा कर रखता है।
Cairo में Private view और pure functions
Solidity में, “private” और “internal” function के बीच का अंतर यह है कि चाइल्ड कॉन्ट्रैक्ट्स एक “internal” function को देख सकते हैं, लेकिन एक “private” function केवल उस कॉन्ट्रैक्ट द्वारा देखा जा सकता है जिसमें वह function होता है।
Cairo में इनहेरिटेंस (inheritance) नहीं है, इसलिए जब हम Cairo में “private” function का उल्लेख करते हैं तो हमें सावधान रहना होगा।
हालाँकि, एक स्वाभाविक प्रश्न उठता है: क्या functions की visibility को “modularize” करना संभव है? उदाहरण के लिए, Solidity में, मान लीजिए कि हमारे पास निम्नलिखित सेटअप है:
contract A {
function private_magic_number() private returns (uint256) {
return 6;
}
function internal_mul_by_magic_number(uint256 x) internal returns (uint256) {
return x * private_magic_number()
}
}
contract B is A {
function external_fun() external returns (uint256) {
return internal_mul_by_magic_number();
}
}
कॉन्ट्रैक्ट B, function internal_mul_by_magic_number() को “देख” सकता है क्योंकि यह A को इनहेरिट करता है; B, private_magic_number() को नहीं देख सकता।
हालाँकि, B में external_fun(), “पर्दे के पीछे (behind the scenes)” private_magic_number() का उपयोग करता है जब यह internal_mul_by_magic_number() को कॉल करता है।
आइए Cairo में एक समान संरचना (construct) बनाएं ताकि यह दिखाया जा सके कि कैसे कोई function कोड के अन्य हिस्सों के लिए ऑफ-लिमिट (off-limits) हो सकता है, ठीक वैसे ही जैसे Solidity में एक private function काम करता है।
Private functions के लिए nested modules का उपयोग करना
अब तक, हमने केवल कॉन्ट्रैक्ट functions के लिए एक “कंटेनर” के रूप में mod (“module”) को देखा है। हालाँकि, Cairo हमें आगे के मॉड्युलराइजेशन के लिए nested modules का उपयोग करने की अनुमति देता है। हम Solidity में private functions के समान कार्यक्षमता प्राप्त करने के लिए इस पैटर्न का उपयोग कर सकते हैं।
नीचे डिफ़ॉल्ट कॉन्ट्रैक्ट स्ट्रक्चर दिया गया है जब आप एक नया Scarb (snfoundry) प्रोजेक्ट जनरेट करते हैं, लेकिन एक आंतरिक mod के साथ जिसमें internal_mul_by_magic_number() और private_magic_number() functions शामिल हैं।
इस आंतरिक (inner) मॉड्यूल को कॉन्ट्रैक्ट के अंत में घोषित किया गया है, इसलिए आप मुख्य परिवर्तनों को देखने के लिए सीधे वहां स्क्रॉल कर सकते हैं:
/// Interface representing `HelloContract`.
/// This interface allows modification and retrieval of the contract balance.
#[starknet::interface]
pub trait IHelloStarknet<TContractState> {
/// Increase contract balance.
fn increase_balance(ref self: TContractState, amount: felt252);
/// Retrieve contract balance.
fn get_balance(self: @TContractState) -> felt252;
fn get_balance_6x(self: @TContractState) -> felt252;
}
/// Simple contract for managing balance.
#[starknet::contract]
mod HelloStarknet {
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
#[storage]
struct Storage {
balance: felt252,
}
#[abi(embed_v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
fn increase_balance(ref self: ContractState, amount: felt252) {
assert(amount != 0, 'Amount cannot be 0');
self.balance.write(self.balance.read() + amount);
}
fn get_balance(self: @ContractState) -> felt252 {
self.balance.read()
}
fn get_balance_6x(self: @ContractState) -> felt252 {
rare_library::internal_mul_by_magic_number(self.balance.read())
}
}
// ~~~~~~~~~~~~~~~~~~~~~
// ~ MOD INSERTED HERE ~
// ~~~~~~~~~~~~~~~~~~~~~
mod rare_library {
pub fn internal_mul_by_magic_number(a: felt252) -> felt252 {
a * private_magic_number()
}
fn private_magic_number() -> felt252 {
6
}
}
}
ध्यान दें कि न तो internal_mul_by_magic_number और न ही private_magic_number functions @self ContractState के माध्यम से स्टेट को एक्सेस करते हैं, और इसलिए, Solidity के दृष्टिकोण से, इन्हें pure functions माना जाता है।
यह भी ध्यान दें कि internal_mul_by_magic_number() को pub के रूप में मार्क किया गया है लेकिन private_magic_number() में pub नहीं है। इसका मतलब यह है कि rare_library के भीतर के functions private_magic_number() को कॉल कर सकते हैं, लेकिन मॉड्यूल के बाहर के functions नहीं कर सकते। चूँकि internal_mul_by_magic_number को pub के साथ मार्क किया गया है, इसलिए इसे mod के बाहर कॉल किया जा सकता है।
एक्सरसाइज़ (Exercise): get_balance() function से private_magic_number() को कॉल करने का प्रयास करें। आपको एक compilation error मिलना चाहिए जो इस बात की पुष्टि करेगा कि function अपने मॉड्यूल के बाहर एक्सेस नहीं किया जा सकता है।
चूँकि private_magic_number() को मॉड्यूल rare_library के बाहर से किसी भी चीज़ द्वारा कॉल नहीं किया जा सकता है, इसलिए हम इसे एक private function मान सकते हैं।
mod को एक अलग फ़ाइल में ले जाना
इनलाइन mod ब्लॉक छोटे मॉड्यूल्स के लिए अच्छा काम करते हैं, लेकिन जैसे-जैसे मॉड्यूल बढ़ता है, वे आपकी कॉन्ट्रैक्ट फ़ाइल को अव्यवस्थित (clutter) कर सकते हैं। जब आपको कई मॉड्यूल्स की आवश्यकता होती है, जिनमें से प्रत्येक के अपने functions होते हैं, तो मुख्य कॉन्ट्रैक्ट फ़ाइल में सब कुछ रखने से विशिष्ट लॉजिक को खोजना कठिन हो जाता है।
आइए rare_library मॉड्यूल को एक अलग फ़ाइल में ले जाने के लिए अपने कोड को रीफैक्टर (refactor) करें। यह लाइब्रेरी के मॉड्यूल इम्प्लीमेंटेशन्स को अलग (isolate) करते हुए कॉन्ट्रैक्ट फ़ाइल को कॉन्ट्रैक्ट लॉजिक पर केंद्रित रखता है। हम पिछले सेक्शन के internal_demo प्रोजेक्ट के साथ काम करना जारी रखेंगे।
एक अलग मॉड्यूल फ़ाइल बनाएं
src/ डायरेक्टरी के अंदर, rare_lib.cairo नामक एक नई फ़ाइल बनाएं और निम्नलिखित functions जोड़ें:
pub fn internal_mul_by_magic_number(a: felt252) -> felt252 {
a * private_magic_number()
}
fn private_magic_number() -> felt252 {
6
}
ध्यान दें कि चूँकि हम एक अलग फ़ाइल में हैं, इसलिए अब functions को mod के साथ रैप (wrap) करने की कोई आवश्यकता नहीं है; फ़ाइल स्वयं मॉड्यूल के रूप में कार्य करती है।
src/lib.cairo को अपडेट करें
अब हमें अपने नए एक्सटर्नल मॉड्यूल का उपयोग करने के लिए src/lib.cairo को अपडेट करने की आवश्यकता है। निम्नलिखित परिवर्तन करें:
lib.cairoके शीर्ष (top) पर मॉड्यूल घोषित करें
mod rare_lib;
- उस “internal” function को इम्पोर्ट करें जिसका हम उपयोग करना चाहते हैं:
use crate::rare_lib::{internal_mul_by_magic_number};
- इम्प्लीमेंटेशन में एक नया function
get_balance_6x()जोड़ें:
fn get_balance_6x(self: @ContractState) -> felt252 {
internal_mul_by_magic_number(self.balance.read())
}
- function को इंटरफ़ेस trait में जोड़ें (अन्यथा यह कंपाइल नहीं होगा):
#[starknet::interface]
pub trait IHelloStarknet<TContractState> {
fn increase_balance(ref self: TContractState, amount: felt252);
fn get_balance(self: @TContractState) -> felt252;
fn get_balance_6x(self: @TContractState) -> felt252; // ADD THIS LINE
}
- इनलाइन
rare_librarymod को हटा दें जो हमारे पास पिछले सेक्शन में था (चूंकि हमने इसे अपनी फ़ाइल में स्थानांतरित कर दिया है)।
यहाँ दिया गया है कि src/lib.cairo कैसा दिखना चाहिए:
mod rare_lib;
/// Interface representing 'HelloContract'.
/// This interface allows modification and retrieval of the contract balance.
#[starknet::interface]
pub trait IHelloStarknet<TContractState> {
/// Increase contract balance.
fn increase_balance(ref self: TContractState, amount: felt252);
/// Retrieve contract balance.
fn get_balance(self: @TContractState) -> felt252;
fn get_balance_6x(self: @TContractState) -> felt252;
}
/// Simple contract for managing balance.
#[starknet::contract]
mod HelloStarknet {
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
use crate::rare_lib::{internal_mul_by_magic_number};
#[storage]
struct Storage {
balance: felt252,
}
#[abi(embed_v0)]
impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
fn increase_balance(ref self: ContractState, amount: felt252) {
assert(amount != 0, 'Amount cannot be 0');
self.balance.write(self.balance.read() + amount);
}
fn get_balance(self: @ContractState) -> felt252 {
self.balance.read()
}
fn get_balance_6x(self: @ContractState) -> felt252 {
internal_mul_by_magic_number(self.balance.read())
}
}
}
परिवर्तन नीचे हाइलाइट किए गए हैं:

अब, यह देखने के लिए टेस्ट फ़ाइल में निम्नलिखित टेस्ट केस जोड़ें कि get_balance_6x बैलेंस को मैजिक नंबर से गुणा कर रहा है, भले ही functions एक अलग फ़ाइल में हों:
use starknet::ContractAddress;
use snforge_std::{declare, ContractClassTrait, DeclareResultTrait};
use internal_demo::IHelloStarknetDispatcher;
use internal_demo::IHelloStarknetDispatcherTrait;
fn deploy_contract(name: ByteArray) -> ContractAddress {
let contract = declare(name).unwrap().contract_class();
let (contract_address, _) = contract.deploy(@ArrayTrait::new()).unwrap();
contract_address
}
#[test]
fn test_increase_balance() {
let contract_address = deploy_contract("HelloStarknet");
let dispatcher = IHelloStarknetDispatcher { contract_address };
let balance_before = dispatcher.get_balance();
assert(balance_before == 0, 'Invalid balance');
dispatcher.increase_balance(42);
let balance_after = dispatcher.get_balance();
assert(balance_after == 42, 'Invalid balance');
}
// NEWLY ADDED //
#[test]
fn test_balance_x6() {
// Deploy the HelloStarknet contract
let contract_address = deploy_contract("HelloStarknet");
// Create a dispatcher to interact with the contract
let dispatcher = IHelloStarknetDispatcher { contract_address };
// Verify initial balance is 0
let balance_before = dispatcher.get_balance();
assert(balance_before == 0, 'Invalid balance');
// Increase balance by 1
dispatcher.increase_balance(1);
// Call get_balance_6x which uses the internal function
// Should return 1 * 6 = 6 (multiplied by the magic number)
let balance_after_6x = dispatcher.get_balance_6x();
assert(balance_after_6x == 6, 'Invalid balance');
}
टेस्ट रन करें:
scarb test test_balance_6x
आपको देखना चाहिए कि यह पास हो जाता है, जो इस बात की पुष्टि करता है कि हमारा रीफैक्टर किया गया मॉड्यूलर स्ट्रक्चर सही ढंग से काम करता है।
निष्कर्ष (Wrapping up)
इस लेख में हमने बनाया:
- एक internal view function:
get_balance_2x()(कॉन्ट्रैक्ट स्टेट पढ़ सकता है) - एक internal pure function:
internal_mul_by_magic_number()(स्टेट एक्सेस नहीं कर सकता) - एक private pure function:
private_magic_number()(स्टेट एक्सेस नहीं कर सकता)
Functions तब pure होते हैं जब वे self: @ContractState को पैरामीटर के रूप में नहीं लेते हैं, जिसका अर्थ है कि वे कॉन्ट्रैक्ट स्टोरेज को पढ़ या लिख नहीं सकते हैं।
नोट (Note): हमने कोई private function नहीं बनाया जो स्टेट को देख सके। हालांकि nested modules में functions को self: @ContractState पास करके तकनीकी रूप से ऐसा करना संभव है, यह एक सामान्य पैटर्न नहीं है। व्यवहार में, स्टेट-देखने वाले (state-viewing) functions को आमतौर पर private functions (nested modules में) के बजाय internal functions (अलग impl ब्लॉक में) के रूप में रखा जाता है, क्योंकि internal functions पहले से ही अधिकांश उपयोग मामलों के लिए पर्याप्त एनकैप्सुलेशन (encapsulation) प्रदान करते हैं।
सारांश (Summary)
- internal functions बनाने के लिए, एक अलग
implब्लॉक (बिना#[abi(embed_v0)]के) परिभाषित करें और#[generate_trait]एट्रिब्यूट जोड़ें। यह स्वचालित रूप से एक trait जनरेट करता है, जिससे ये functions कॉन्ट्रैक्ट के लिए internal बने रहते हैं। - एक pure function (जो स्टेट को एक्सेस नहीं कर सकता) बनाने के लिए, कॉन्ट्रैक्ट के अंदर एक
modघोषित करें। फिर आंतरिकmodके अंदर एकpub fnबनाएं। यह function बाहरीmodके लिए एक्सेस करने योग्य होगा, लेकिन किसी और चीज़ के लिए नहीं। - एक
modको दूसरी फ़ाइल में रखा जा सकता है और इम्पोर्ट किया जा सकता है। केवलpubfunctions ही बाहर से दिखाई देंगे।
यह लेख Cairo Programming on Starknet पर एक ट्यूटोरियल सीरीज़ का हिस्सा है।